Skip to content

Commit

Permalink
Merge pull request #483 from geoadmin/feat-pb-1157-hreflang
Browse files Browse the repository at this point in the history
PB-1157 Add hreflang to Links
  • Loading branch information
schtibe authored Dec 12, 2024
2 parents 728c281 + 4e3decb commit 0bbbf34
Show file tree
Hide file tree
Showing 16 changed files with 606 additions and 276 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ django-admin-autocomplete-filter = "~=0.7.1"
django-pgtrigger = "~=4.11.1"
logging-utilities = "~=4.5.0"
django-environ = "*"
language-tags = "*"

[requires]
python_version = "3.12"
10 changes: 9 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions app/stac_api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,18 @@ class ProviderInline(admin.TabularInline):
}


class CollectionLinkInline(admin.TabularInline):
class LinkInline(admin.TabularInline):

def formfield_for_dbfield(self, db_field, request, **kwargs):
# make the hreflang field a bit shorter so that the inline
# will not be rendered too wide
if db_field.attname == 'hreflang':
attrs = {'size': 10}
kwargs['widget'] = forms.TextInput(attrs=attrs)
return super().formfield_for_dbfield(db_field, request, **kwargs)


class CollectionLinkInline(LinkInline):
model = CollectionLink
extra = 0

Expand Down Expand Up @@ -154,7 +165,7 @@ def get_readonly_fields(self, request, obj=None):
return self.readonly_fields


class ItemLinkInline(admin.TabularInline):
class ItemLinkInline(LinkInline):
model = ItemLink
extra = 0

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 5.0.8 on 2024-12-05 10:23

from django.db import migrations
from django.db import models


class Migration(migrations.Migration):

dependencies = [
('stac_api', '0057_item_forecast_duration_item_forecast_horizon_and_more'),
]

operations = [
migrations.AddField(
model_name='collectionlink',
name='hreflang',
field=models.CharField(blank=True, max_length=32, null=True),
),
migrations.AddField(
model_name='itemlink',
name='hreflang',
field=models.CharField(blank=True, max_length=32, null=True),
),
migrations.AddField(
model_name='landingpagelink',
name='hreflang',
field=models.CharField(blank=True, max_length=32, null=True),
),
]
11 changes: 11 additions & 0 deletions app/stac_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import time
from uuid import uuid4

from language_tags import tags
from multihash import encode as multihash_encode
from multihash import to_hex_string

Expand Down Expand Up @@ -153,13 +154,23 @@ class Link(models.Model):
# added link_ to the fieldname, as "type" is reserved
link_type = models.CharField(blank=True, null=True, max_length=150)
title = models.CharField(blank=True, null=True, max_length=255)
hreflang = models.CharField(blank=True, null=True, max_length=32)

class Meta:
abstract = True

def __str__(self):
return f'{self.rel}: {self.href}'

def save(self, *args, **kwargs) -> None:
"""Validate the hreflang"""
self.full_clean()

if self.hreflang is not None and self.hreflang != '' and not tags.check(self.hreflang):
raise ValidationError(_(", ".join([v.message for v in tags.tag(self.hreflang).errors])))

super().save(*args, **kwargs)


class LandingPage(models.Model):
# using "name" instead of "id", as "id" has a default meaning in django
Expand Down
2 changes: 1 addition & 1 deletion app/stac_api/serializers/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CollectionLinkSerializer(NonNullModelSerializer):

class Meta:
model = CollectionLink
fields = ['href', 'rel', 'title', 'type']
fields = ['href', 'rel', 'title', 'type', 'hreflang']

# NOTE: when explicitely declaring fields, we need to add the validation as for the field
# in model !
Expand Down
2 changes: 1 addition & 1 deletion app/stac_api/serializers/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class ItemLinkSerializer(NonNullModelSerializer):

class Meta:
model = ItemLink
fields = ['href', 'rel', 'title', 'type']
fields = ['href', 'rel', 'title', 'type', 'hreflang']

# NOTE: when explicitely declaring fields, we need to add the validation as for the field
# in model !
Expand Down
19 changes: 16 additions & 3 deletions app/stac_api/serializers/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
from collections import OrderedDict
from typing import Dict
from typing import List

from django.utils.dateparse import parse_duration
from django.utils.duration import duration_iso_string
Expand All @@ -8,14 +10,22 @@
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnDict

from stac_api.models import Collection
from stac_api.models import Item
from stac_api.models import Link
from stac_api.utils import build_asset_href
from stac_api.utils import get_browser_url
from stac_api.utils import get_url

logger = logging.getLogger(__name__)


def update_or_create_links(model, instance, instance_type, links_data):
def update_or_create_links(
model: type[Link],
instance: type[Item] | type[Collection],
instance_type: str,
links_data: List[Dict]
):
'''Update or create links for a model
Update the given links list within a model instance or create them when they don't exists yet.
Expand All @@ -27,13 +37,16 @@ def update_or_create_links(model, instance, instance_type, links_data):
'''
links_ids = []
for link_data in links_data:
link: Link
created: bool
link, created = model.objects.get_or_create(
**{instance_type: instance},
rel=link_data["rel"],
defaults={
'href': link_data.get('href', None),
'link_type': link_data.get('link_type', None),
'title': link_data.get('title', None)
'title': link_data.get('title', None),
'hreflang': link_data.get('hreflang', None)
}
)
logger.debug(
Expand All @@ -44,7 +57,7 @@ def update_or_create_links(model, instance, instance_type, links_data):
instance_type: instance.name, "link": link_data
}
)
links_ids.append(link.id)
links_ids.append(link.pk)
# the duplicate here is necessary to update the values in
# case the object already exists
link.link_type = link_data.get('link_type', link.link_type)
Expand Down
11 changes: 8 additions & 3 deletions app/tests/tests_10/data_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ def get_last_name(cls, last, extension=''):
)
return last

def create_sample(self, sample, name=None, db_create=False, **kwargs):
def create_sample(self, sample, name=None, db_create=False, **kwargs) -> SampleData:
'''Create a data sample
Args:
Expand Down Expand Up @@ -1356,8 +1356,13 @@ def __init__(self):
self.collection_assets = CollectionAssetFactory()

def create_collection_sample(
self, name=None, sample='collection-1', db_create=False, required_only=False, **kwargs
):
self,
name=None,
sample='collection-1',
db_create=False,
required_only=False,
**kwargs
) -> SampleData:
'''Create a collection data sample
Args:
Expand Down
21 changes: 21 additions & 0 deletions app/tests/tests_10/sample_data/collection_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@
}
}

links_hreflanged = {
'link-1': {
'title': 'Link with hreflang',
'rel': 'describedBy',
'href': 'http://perdu.com/',
'hreflang': 'de'
},
'link-2': {
'title': 'Link with hreflang',
'rel': 'copiedFrom',
'href': 'http://perdu.com/',
'hreflang': 'fr-CH'
}
}

collections = {
'collection-1': {
'name': 'collection-1',
Expand Down Expand Up @@ -104,6 +119,12 @@
'license': 'proprietary',
'links': [multiple_links['link-1'], multiple_links['link-2']]
},
'collection-hreflang-links': {
'name': 'collection-1',
'description': 'This a collection description',
'license': 'proprietary',
'links': links_hreflanged.values()
},
'collection-invalid-providers': {
'name': 'collection-invalid-provider',
'description': 'This is a collection with invalid provider',
Expand Down
33 changes: 33 additions & 0 deletions app/tests/tests_10/sample_data/item_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@
}
}

links_hreflanged = {
'link-1': {
'title': 'Link with hreflang',
'rel': 'describedBy',
'href': 'http://perdu.com/',
'hreflang': 'de'
},
'link-2': {
'title': 'Link with hreflang',
'rel': 'copiedFrom',
'href': 'http://perdu.com/',
'hreflang': 'fr-CH'
}
}

items = {
'item-1': {
'name': 'item-1',
Expand Down Expand Up @@ -151,6 +166,24 @@
},
'links': links_invalid.values()
},
'item-hreflang-links': {
'name': 'item-hreflang-link',
'geometry':
GEOSGeometry(
json.dumps({
"coordinates": [[
[5.644711, 46.775054],
[5.644711, 48.014995],
[6.602408, 48.014995],
[7.602408, 49.014995],
[5.644711, 46.775054],
]],
"type": "Polygon"
})
),
'properties_datetime': fromisoformat('2024-12-05T13:37Z'),
'links': links_hreflanged.values()
},
'item-switzerland': {
'name': 'item-switzerland',
'geometry': geometries['switzerland'],
Expand Down
Loading

0 comments on commit 0bbbf34

Please sign in to comment.