diff --git a/djangoldp/__init__.py b/djangoldp/__init__.py index 7461cf54a1e49fb4cd6b0127bd1e306427c2c56a..8c3166a1a75b9cbb5b3268b3dc537289c3d72a68 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/models.py b/djangoldp/models.py index fff174f810d49c335f6a2124bb808072a86250f6..e8a79806abbdfa7df734fd7ecad6a31efd804b0c 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 e1ae36cb66b5965f07cfac05f791d7db89296e7d..539ec66820ffb9c094d7b028591c31801164b260 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 @@ -363,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] @@ -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 981e5158b1a2d34472e4e8385994166e1fc080c5..43e56d6614b626c700a6704166cd577fa143ec3a 100644 --- a/djangoldp/tests/models.py +++ b/djangoldp/tests/models.py @@ -1,17 +1,31 @@ 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 -class Skill(models.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 JobOffer(models.Model): + class Meta: + serializer_fields=["@id", "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 = 'slug' class Thread(models.Model): @@ -27,6 +41,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): @@ -51,3 +66,6 @@ class Task(models.Model): class Meta: serializer_fields = ['@id', 'title', 'batch'] + + + diff --git a/djangoldp/tests/tests_ldp_model.py b/djangoldp/tests/tests_ldp_model.py index 329899c008d2d184eb9434b78d49ddf6ef8b702d..3e1da6a715f946324d464945551ff42ce2a38f82 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 342448e41664c8967cf098bd63c329890dd36554..0d7b70b96ce59d940728e18ee54f8c8ee6dfcf38 100644 --- a/djangoldp/tests/tests_save.py +++ b/djangoldp/tests/tests_save.py @@ -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": [ diff --git a/djangoldp/tests/tests_update.py b/djangoldp/tests/tests_update.py index 39083e2038e831663e52137251f5cd9fec1fdfce..19c96eff6a7d4eb70d1a9516ed1f0c9cac2d530f 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"}, ] } diff --git a/djangoldp/tests/urls.py b/djangoldp/tests/urls.py index 5062860e7ecfa04507fc3f8f5272efd3340108c4..7ac1c38f725a1f83c50bc1393bc37c28259f43b1 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 59efe9c957f2466c8883695dd6ca03d8c8ea37d2..1dcce95e752cd2d96e8b62c6f2b5bcadf5ef2e4e 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', []))))))