From b58aa3cbe922451230187380ce2aaa239041ebb6 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste <bleme@pm.me> Date: Wed, 13 Mar 2019 12:29:44 +0100 Subject: [PATCH 1/3] syntax: issue #101 failed unit test --- djangoldp/__init__.py | 2 +- djangoldp/tests/models.py | 14 ++++++++++++-- djangoldp/tests/tests_ldp_model.py | 6 +++--- djangoldp/tests/tests_save.py | 3 ++- djangoldp/tests/urls.py | 4 +--- djangoldp/urls.py | 1 + 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/djangoldp/__init__.py b/djangoldp/__init__.py index 7461cf54..8c3166a1 100644 --- a/djangoldp/__init__.py +++ b/djangoldp/__init__.py @@ -1,4 +1,4 @@ from django.db.models import options __version__ = '0.0.0' -options.DEFAULT_NAMES += ('rdf_type', 'rdf_context', 'auto_author', 'view_set', 'container_path', 'permission_classes', 'serializer_fields', 'nested_fields') \ No newline at end of file +options.DEFAULT_NAMES += ('lookup_field', 'rdf_type', 'rdf_context', 'auto_author', 'view_set', 'container_path', 'permission_classes', 'serializer_fields', 'nested_fields') \ No newline at end of file diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py index 981e5158..e29ee17c 100644 --- a/djangoldp/tests/models.py +++ b/djangoldp/tests/models.py @@ -4,15 +4,24 @@ from django.db import models from djangoldp.models import Model -class Skill(models.Model): +class Skill(Model): title = models.CharField(max_length=255, blank=True, null=True) obligatoire = models.CharField(max_length=255) + class Meta: + serializer_fields=["@id", "title"] + lookup_field = 'title' + -class JobOffer(models.Model): +class JobOffer(Model): title = models.CharField(max_length=255, blank=True, null=True) skills = models.ManyToManyField(Skill, blank=True) + class Meta: + nested_fields=["skills"] + container_path="job-offers/" + lookup_field = 'title' + class Thread(models.Model): description = models.CharField(max_length=255, blank=True, null=True) @@ -27,6 +36,7 @@ class Message(models.Model): class Dummy(models.Model): some = models.CharField(max_length=255, blank=True, null=True) + slug = models.SlugField(blank=True, null=True, unique=True) class LDPDummy(Model): diff --git a/djangoldp/tests/tests_ldp_model.py b/djangoldp/tests/tests_ldp_model.py index 329899c0..3e1da6a7 100644 --- a/djangoldp/tests/tests_ldp_model.py +++ b/djangoldp/tests/tests_ldp_model.py @@ -11,7 +11,7 @@ class LDPModelTest(TestCase): def test_class_not_inheriting_ldp_model(self): dummy = Dummy.objects.create(some="text") self.assertEquals("/dummys/", Model.container_id(dummy)) - self.assertEquals("/dummys/{}".format(dummy.pk), Model.resource_id(dummy)) + self.assertEquals("/dummys/{}".format(dummy.slug), Model.resource_id(dummy)) def test_class_inheriting_ldp_model(self): dummy = LDPDummy.objects.create(some="text") @@ -21,8 +21,8 @@ class LDPModelTest(TestCase): self.assertEquals("/ldpdummys/{}".format(dummy.pk), Model.resource_id(dummy)) def test_from_resolve_id(self): - saved_instance = Dummy.objects.create(some="text") - result = Model.resolve_id("/dummys/{}".format(saved_instance.pk)) + saved_instance = Dummy.objects.create(some="text", slug="someid") + result = Model.resolve_id("/dummys/{}".format(saved_instance.slug)) self.assertEquals(saved_instance, result) def test_resolve_container(self): diff --git a/djangoldp/tests/tests_save.py b/djangoldp/tests/tests_save.py index 342448e4..161e45f5 100644 --- a/djangoldp/tests/tests_save.py +++ b/djangoldp/tests/tests_save.py @@ -1,7 +1,7 @@ from django.test import TestCase from djangoldp.serializers import LDPSerializer -from djangoldp.tests.models import Skill, JobOffer, Invoice +from djangoldp.tests.models import Skill, JobOffer, Invoice, Dummy class Save(TestCase): @@ -144,3 +144,4 @@ class Save(TestCase): self.assertIs(result.joboffer_set.count(), 1) self.assertEquals(result.joboffer_set.get(), job) self.assertIs(result.joboffer_set.get().skills.count(), 1) + diff --git a/djangoldp/tests/urls.py b/djangoldp/tests/urls.py index 5062860e..7ac1c38f 100644 --- a/djangoldp/tests/urls.py +++ b/djangoldp/tests/urls.py @@ -5,12 +5,10 @@ from djangoldp.tests.models import Skill, JobOffer, Message, Thread, Dummy from djangoldp.views import LDPViewSet urlpatterns = [ - url(r'^skills/', LDPViewSet.urls(model=Skill, permission_classes=[], fields=["@id", "title"], nested_fields=[])), - url(r'^job-offers/', LDPViewSet.urls(model=JobOffer, nested_fields=["skills"], permission_classes=())), url(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[], fields=["@id", "text"], nested_fields=[])), url(r'^threads/', LDPViewSet.urls(model=Thread, nested_fields=["message_set"], permission_classes=())), url(r'^users/', LDPViewSet.urls(model=settings.AUTH_USER_MODEL, permission_classes=[])), - url(r'^dummys/', LDPViewSet.urls(model=Dummy, permission_classes=[])), + url(r'^dummys/', LDPViewSet.urls(model=Dummy, permission_classes=[], lookup_field='slug',)), url(r'^', include('djangoldp.urls')), ] diff --git a/djangoldp/urls.py b/djangoldp/urls.py index 59efe9c9..1dcce95e 100644 --- a/djangoldp/urls.py +++ b/djangoldp/urls.py @@ -30,6 +30,7 @@ for class_name in model_classes: urls_fct = model_class.get_view_set().urls urlpatterns.append(url(r'^' + path, include( urls_fct(model=model_class, + lookup_field=getattr(model_class._meta, 'lookup_field', getattr(model_class.Meta, 'lookup_field', [])), permission_classes=getattr(model_class._meta, 'permission_classes', getattr(model_class.Meta, 'permission_classes', [])), fields=getattr(model_class._meta, 'serializer_fields', getattr(model_class.Meta, 'serializer_fields', [])), nested_fields=getattr(model_class._meta, 'nested_fields', getattr(model_class.Meta, 'nested_fields', [])))))) -- GitLab From 77e5f8fe32d3690b59fb570b41de6d0f3105fc47 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste <bleme@pm.me> Date: Wed, 13 Mar 2019 13:11:59 +0100 Subject: [PATCH 2/3] update: manage slug on save and update --- djangoldp/models.py | 6 +++- djangoldp/serializers.py | 19 +++++++----- djangoldp/tests/models.py | 12 ++++++-- djangoldp/tests/tests_save.py | 17 +++++------ djangoldp/tests/tests_update.py | 52 ++++++++++++++++----------------- 5 files changed, 61 insertions(+), 45 deletions(-) diff --git a/djangoldp/models.py b/djangoldp/models.py index fff174f8..e8a79806 100644 --- a/djangoldp/models.py +++ b/djangoldp/models.py @@ -29,11 +29,15 @@ class Model(models.Model): @classmethod def resource_id(cls, instance): + return "{}{}".format(cls.container_id(instance), getattr(instance, cls.slug_field(instance))) + + @classmethod + def slug_field(cls, instance): view_name = '{}-detail'.format(instance._meta.object_name.lower()) slug_field = '/{}'.format(get_resolver().reverse_dict[view_name][0][0][1][0]) if slug_field.startswith('/'): slug_field = slug_field[1:] - return "{}{}".format(cls.container_id(instance), getattr(instance, slug_field)) + return slug_field @classmethod def container_id(cls, instance): diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py index e1ae36cb..f6a8c821 100644 --- a/djangoldp/serializers.py +++ b/djangoldp/serializers.py @@ -307,7 +307,8 @@ class LDPSerializer(HyperlinkedModelSerializer): try: match = resolve(uri_to_iri(uri)) - ret['pk'] = match.kwargs['pk'] + slug_field = Model.slug_field(self.__class__.Meta.model) + ret[slug_field] = match.kwargs[slug_field] except Resolver404: pass @@ -389,9 +390,11 @@ class LDPSerializer(HyperlinkedModelSerializer): for attr, value in validated_data.items(): if isinstance(value, dict): + slug_field = Model.slug_field(instance) manager = getattr(instance, attr) - if 'pk' in value: - oldObj = manager._meta.model.objects.get(pk=value['pk']) + if slug_field in value: + kwargs = {slug_field: value[slug_field]} + oldObj = manager._meta.model.objects.get(**kwargs) value = self.update(instance=oldObj, validated_data=value) else: value = self.internal_create(validated_data=value, model=manager._meta.model) @@ -406,18 +409,20 @@ class LDPSerializer(HyperlinkedModelSerializer): def save_or_update_nested_list(self, instance, nested_fields): for (field_name, data) in nested_fields: manager = getattr(instance, field_name) + slug_field = Model.slug_field(instance) - item_pk_to_keep = list(map(lambda e: int(e['pk']), filter(lambda x: 'pk' in x, data))) + item_pk_to_keep = list(map(lambda e: e[slug_field], filter(lambda x: slug_field in x, data))) for item in list(manager.all()): - if not item.pk in item_pk_to_keep: + if not str(getattr(item, slug_field)) in item_pk_to_keep: if getattr(manager, 'through', None) is None: item.delete() else: manager.remove(item) for item in data: - if 'pk' in item: - oldObj = manager.model.objects.get(pk=item['pk']) + if slug_field in item: + kwargs = {slug_field: item[slug_field]} + oldObj = manager.model.objects.get(**kwargs) savedItem = self.update(instance=oldObj, validated_data=item) else: rel = getattr(instance._meta.model, field_name).rel diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py index e29ee17c..43e56d66 100644 --- a/djangoldp/tests/models.py +++ b/djangoldp/tests/models.py @@ -1,5 +1,7 @@ from django.conf import settings from django.db import models +from django.db.models.signals import pre_save +from django.dispatch import receiver from djangoldp.models import Model @@ -7,20 +9,23 @@ from djangoldp.models import Model class Skill(Model): title = models.CharField(max_length=255, blank=True, null=True) obligatoire = models.CharField(max_length=255) + slug = models.SlugField(blank=True, null=True, unique=True) + class Meta: serializer_fields=["@id", "title"] - lookup_field = 'title' + lookup_field = 'slug' class JobOffer(Model): title = models.CharField(max_length=255, blank=True, null=True) skills = models.ManyToManyField(Skill, blank=True) + slug = models.SlugField(blank=True, null=True, unique=True) class Meta: nested_fields=["skills"] container_path="job-offers/" - lookup_field = 'title' + lookup_field = 'slug' class Thread(models.Model): @@ -61,3 +66,6 @@ class Task(models.Model): class Meta: serializer_fields = ['@id', 'title', 'batch'] + + + diff --git a/djangoldp/tests/tests_save.py b/djangoldp/tests/tests_save.py index 161e45f5..0d7b70b9 100644 --- a/djangoldp/tests/tests_save.py +++ b/djangoldp/tests/tests_save.py @@ -1,7 +1,7 @@ from django.test import TestCase from djangoldp.serializers import LDPSerializer -from djangoldp.tests.models import Skill, JobOffer, Invoice, Dummy +from djangoldp.tests.models import Skill, JobOffer, Invoice class Save(TestCase): @@ -41,19 +41,19 @@ class Save(TestCase): self.assertEquals(result.batches.all()[0].tasks.all()[0].title, "Tache 1") def test_save_m2m(self): - skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire") - skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire") + skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire", slug="slug1") + skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire", slug="slug2") job = {"title": "job test", "skills": { "ldp:contains": [ - {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)}, - {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk), "title": "skill2 UP"}, - {"title": "skill3 NEW", "obligatoire": "obligatoire"}, + {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.slug)}, + {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.slug), "title": "skill2 UP"}, + {"title": "skill3", "obligatoire": "obligatoire", "slug": "slug3"}, ]} } - meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")} + meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills", "slug")} meta_class = type('Meta', (), meta_args) serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class}) @@ -65,7 +65,7 @@ class Save(TestCase): self.assertIs(result.skills.count(), 3) self.assertEquals(result.skills.all()[0].title, "skill1") # no change self.assertEquals(result.skills.all()[1].title, "skill2 UP") # title updated - self.assertEquals(result.skills.all()[2].title, "skill3 NEW") # creation on the fly + self.assertEquals(result.skills.all()[2].title, "skill3") # creation on the fly def test_save_m2m_graph_simple(self): job = {"@graph": [ @@ -144,4 +144,3 @@ class Save(TestCase): self.assertIs(result.joboffer_set.count(), 1) self.assertEquals(result.joboffer_set.get(), job) self.assertIs(result.joboffer_set.get().skills.count(), 1) - diff --git a/djangoldp/tests/tests_update.py b/djangoldp/tests/tests_update.py index 39083e20..19c96eff 100644 --- a/djangoldp/tests/tests_update.py +++ b/djangoldp/tests/tests_update.py @@ -8,19 +8,19 @@ from djangoldp.tests.models import Skill, JobOffer, Thread, Message class Update(TestCase): def test_update(self): - skill = Skill.objects.create(title="to drop", obligatoire="obligatoire") - skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire") - skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire") + skill = Skill.objects.create(title="to drop", obligatoire="obligatoire", slug="slug1") + skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire", slug="slug2") + skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire", slug="slug3") job1 = JobOffer.objects.create(title="job test") job1.skills.add(skill) - job = {"@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk), + job = {"@id": "https://happy-dev.fr/job-offers/{}/".format(job1.slug), "title": "job test updated", "skills": { "ldp:contains": [ {"title": "new skill", "obligatoire": "okay"}, - {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)}, - {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk), "title": "skill2 UP"}, + {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.slug)}, + {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.slug), "title": "skill2 UP"}, ]} } @@ -40,21 +40,21 @@ class Update(TestCase): self.assertEquals(skills[2].title, "skill2 UP") # title updated def test_update_graph(self): - skill = Skill.objects.create(title="to drop", obligatoire="obligatoire") - skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire") - skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire") - job1 = JobOffer.objects.create(title="job test") + skill = Skill.objects.create(title="to drop", obligatoire="obligatoire", slug="slug1") + skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire", slug="slug2") + skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire", slug="slug3") + job1 = JobOffer.objects.create(title="job test", slug="slug4") job1.skills.add(skill) job = {"@graph": [ { - "@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk), + "@id": "https://happy-dev.fr/job-offers/{}/".format(job1.slug), "title": "job test updated", "skills": { "ldp:contains": [ - {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)}, - {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk)}, + {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.slug)}, + {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.slug)}, {"@id": "_.123"}, ]} }, @@ -64,10 +64,10 @@ class Update(TestCase): "obligatoire": "okay" }, { - "@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk), + "@id": "https://happy-dev.fr/skills/{}/".format(skill1.slug), }, { - "@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk), + "@id": "https://happy-dev.fr/skills/{}/".format(skill2.slug), "title": "skill2 UP" } ] @@ -90,19 +90,19 @@ class Update(TestCase): self.assertEquals(skills[2].title, "skill2 UP") # title updated def test_update_graph_2(self): - skill = Skill.objects.create(title="to drop", obligatoire="obligatoire") - skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire") - skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire") - job1 = JobOffer.objects.create(title="job test") + skill = Skill.objects.create(title="to drop", obligatoire="obligatoire", slug="slug") + skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire", slug="slug1") + skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire", slug="slug2") + job1 = JobOffer.objects.create(title="job test", slug="slug1") job1.skills.add(skill) job = {"@graph": [ { - "@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk), + "@id": "https://happy-dev.fr/job-offers/{}/".format(job1.slug), "title": "job test updated", "skills": { - "@id": "https://happy-dev.fr/job-offers/{}/skills/".format(job1.pk) + "@id": "https://happy-dev.fr/job-offers/{}/skills/".format(job1.slug) } }, { @@ -111,17 +111,17 @@ class Update(TestCase): "obligatoire": "okay" }, { - "@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk), + "@id": "https://happy-dev.fr/skills/{}/".format(skill1.slug), }, { - "@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk), + "@id": "https://happy-dev.fr/skills/{}/".format(skill2.slug), "title": "skill2 UP" }, { - '@id': "https://happy-dev.fr/job-offers/{}/skills/".format(job1.pk), + '@id': "https://happy-dev.fr/job-offers/{}/skills/".format(job1.slug), "ldp:contains": [ - {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)}, - {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk)}, + {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.slug)}, + {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.slug)}, {"@id": "_.123"}, ] } -- GitLab From 2706cc44bbd2d2d7e7df1c5a98d0b4734bf60246 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste <bleme@pm.me> Date: Wed, 13 Mar 2019 16:54:25 +0100 Subject: [PATCH 3/3] bugfix: fieldname can be None --- djangoldp/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py index f6a8c821..539ec668 100644 --- a/djangoldp/serializers.py +++ b/djangoldp/serializers.py @@ -364,7 +364,7 @@ class LDPSerializer(HyperlinkedModelSerializer): info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): - if relation_info.to_many and relation_info.reverse and not (field_name in validated_data): + if relation_info.to_many and relation_info.reverse and not (field_name in validated_data) and not field_name is None: rel = getattr(instance._meta.model, field_name).rel if rel.name in validated_data: related = validated_data[rel.name] -- GitLab