diff --git a/README.md b/README.md index e8525f1c41aa65fa6f64b780ded4280a4012a706..17b3fabed37bd158d1522e0a7949e594db04828d 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,14 @@ django-admin startproject myldpserver ``` 3. Create your django model inside a file myldpserver/myldpserver/models.py +Note that container_path will be use to resolve instance iri and container iri +In the future it could also be used to auto configure django router (e.g. urls.py) ``` -from django.db import models +from djangoldp.models import Model -class Todo(models.Model): +class Todo(Model): + container_path = "/my-path/" name = models.CharField(max_length=255) deadline = models.DateTimeField() diff --git a/djangoldp/models.py b/djangoldp/models.py index 50b31c6c8181e7642d9c5dcc11ac1df41b1490d9..f63ffd4b353cfe20a4f157ebb477cbce7549d56c 100644 --- a/djangoldp/models.py +++ b/djangoldp/models.py @@ -1,10 +1,77 @@ from django.conf import settings from django.db import models +from django.urls import get_resolver + + +class Model(models.Model): + container_path = None + + def get_container_path(self): + return self.container_path + + def get_absolute_url(self): + return Model.resource_id(self) + + def get_container_id(self): + return Model.container_id(self) + + @classmethod + def resource_id(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)) + + @classmethod + def container_id(cls, instance): + if isinstance(instance, cls): + path = instance.container_path + else: + view_name = '{}-list'.format(instance._meta.object_name.lower()) + path = get_resolver().reverse(view_name) + + path = cls.__clean_path(path) + + return path + + class Meta: + abstract = True + + @classmethod + def resolve_id(cls, id): + id = cls.__clean_path(id) + view, args, kwargs = get_resolver().resolve(id) + return view.initkwargs['model'].objects.get(**kwargs) + + @classmethod + def resolve_container(cls, path): + path = cls.__clean_path(path) + view, args, kwargs = get_resolver().resolve(path) + return view.initkwargs['model'] + + @classmethod + def resolve(cls, path): + container = cls.resolve_container(path) + try: + resolve_id = cls.resolve_id(path) + except: + resolve_id = None + return container, resolve_id + + @classmethod + def __clean_path(cls, path): + if not path.startswith("/"): + path = "/{}".format(path) + if not path.endswith("/"): + path = "{}/".format(path) + return path + class LDPSource(models.Model): container = models.URLField() federation = models.CharField(max_length=255) - + class Meta: rdf_type = 'sib:source' ordering = ('federation',) @@ -12,7 +79,7 @@ class LDPSource(models.Model): ('view_source', 'acl:Read'), ('control_source', 'acl:Control'), ) - + def __str__(self): return "{}: {}".format(self.federation, self.container) @@ -24,6 +91,7 @@ class LDNotification(models.Model): type = models.CharField(max_length=255) summary = models.TextField() date = models.DateTimeField(auto_now_add=True) + class Meta: permissions = ( ('view_todo', 'Read'), diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py index 815be9068ffdc34903d8dae1af4bd06bca64cae6..18a63a1daf9b6d62885e2bee2849ad7dbe7820bc 100644 --- a/djangoldp/serializers.py +++ b/djangoldp/serializers.py @@ -16,6 +16,8 @@ from rest_framework.settings import api_settings from rest_framework.utils.field_mapping import get_nested_relation_kwargs from rest_framework.utils.serializer_helpers import ReturnDict +from djangoldp.models import Model + class LDListMixin: def to_internal_value(self, data): @@ -40,9 +42,8 @@ class LDListMixin: def get_value(self, dictionary): try: object_list = dictionary["@graph"] - view_name = '{}-list'.format(self.parent.Meta.model._meta.object_name.lower()) - part_id = '/{}'.format(get_resolver().reverse_dict[view_name][0][0][0], self.parent.instance.pk) - obj = next(filter(lambda o: part_id in o['@id'], object_list)) + container_id = Model.container_id(self.parent.instance) + obj = next(filter(lambda o: container_id in o['@id'], object_list)) list = super().get_value(obj) try: list = next(filter(lambda o: list['@id'] == o['@id'], object_list)) @@ -197,9 +198,8 @@ class LDPSerializer(HyperlinkedModelSerializer): def get_value(self, dictionary): try: object_list = dictionary["@graph"] - part_id = '/{}'.format(get_resolver().reverse_dict[self.parent_view_name][0][0][0], - self.parent.instance.pk) - obj = next(filter(lambda o: part_id in o['@id'], object_list)) + resource_id = Model.resource_id(self.parent.instance) + obj = next(filter(lambda o: resource_id in o['@id'], object_list)) return super().get_value(obj) except KeyError: return super().get_value(dictionary) @@ -288,10 +288,8 @@ class LDPSerializer(HyperlinkedModelSerializer): def get_value(self, dictionary): try: object_list = dictionary["@graph"] - view_name = '{}-list'.format(self.parent.Meta.model._meta.object_name.lower()) - part_id = '/{}'.format(get_resolver().reverse_dict[view_name][0][0][0], - self.parent.instance.pk) - obj = next(filter(lambda o: part_id in o[self.url_field_name], object_list)) + container_id = Model.container_path(self.parent.instance) + obj = next(filter(lambda o: container_id in o[self.url_field_name], object_list)) item = super().get_value(obj) full_item = None if item is empty: diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py index 13a0aedf31da3efac36fd9c00bb64b4d73923119..2622673f0cd3a3873b2d7e0d592b6a601414b66e 100644 --- a/djangoldp/tests/models.py +++ b/djangoldp/tests/models.py @@ -1,6 +1,8 @@ from django.conf import settings from django.db import models +from djangoldp.models import Model + class Skill(models.Model): title = models.CharField(max_length=255, blank=True, null=True) @@ -23,3 +25,11 @@ class Message(models.Model): author_user = models.ForeignKey(settings.AUTH_USER_MODEL) +class Dummy(models.Model): + some = models.CharField(max_length=255, blank=True, null=True) + + +class LDPDummy(Model): + some = models.CharField(max_length=255, blank=True, null=True) + container_path = "ldp-dummys" + diff --git a/djangoldp/tests/runner.py b/djangoldp/tests/runner.py index 1cf8604dbae8eda88317a21cac92b33636febbb5..4e3bcd0417a8ea9e8588f2ec0ae9539615278bb8 100644 --- a/djangoldp/tests/runner.py +++ b/djangoldp/tests/runner.py @@ -25,6 +25,7 @@ from django.test.runner import DiscoverRunner test_runner = DiscoverRunner(verbosity=1) failures = test_runner.run_tests([ + 'djangoldp.tests.tests_ldp_model', 'djangoldp.tests.tests_save', 'djangoldp.tests.tests_update']) if failures: diff --git a/djangoldp/tests/tests_ldp_model.py b/djangoldp/tests/tests_ldp_model.py new file mode 100644 index 0000000000000000000000000000000000000000..56bbe55206c0d95c7c90e998ecbda3ad49eb326b --- /dev/null +++ b/djangoldp/tests/tests_ldp_model.py @@ -0,0 +1,39 @@ +import unittest + +from django.test import TestCase + +from djangoldp.models import Model +from djangoldp.tests.models import Dummy, LDPDummy + + +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)) + + def test_class_inheriting_ldp_model(self): + dummy = LDPDummy.objects.create(some="text") + self.assertEquals("/ldp-dummys/", dummy.get_container_id()) + self.assertEquals("/ldp-dummys/{}".format(dummy.pk), dummy.get_absolute_url()) + self.assertEquals("/ldp-dummys/", Model.container_id(dummy)) + self.assertEquals("/ldp-dummys/{}".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)) + self.assertEquals(saved_instance, result) + + def test_resolve_container(self): + result = Model.resolve_container("/dummys/") + self.assertEquals(Dummy, result) + + @unittest.skip("futur feature: avoid urls.py on apps") + def test_auto_url(self): + from django.urls import get_resolver + dummy = LDPDummy.objects.create(some="text") + view_name = '{}-list'.format(dummy._meta.object_name.lower()) + path = '/{}'.format(get_resolver().reverse_dict[view_name][0][0][0], dummy.pk) + + self.assertEquals(path, dummy.get_absolute_url()) diff --git a/djangoldp/tests/tests_save.py b/djangoldp/tests/tests_save.py index 1117c928eb4d5f7908c942666a8a718d87b2c79e..7e05496ab21ab8aff4e639bd6d80dcc5c8873ae5 100644 --- a/djangoldp/tests/tests_save.py +++ b/djangoldp/tests/tests_save.py @@ -4,7 +4,7 @@ from djangoldp.serializers import LDPSerializer from djangoldp.tests.models import Skill, JobOffer -class Serializer(TestCase): +class Save(TestCase): def test_save_m2m(self): skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire") diff --git a/djangoldp/tests/tests_update.py b/djangoldp/tests/tests_update.py index 7b232b4455e057d97544dac01999657d2d104ca4..622b55ef6c6b2374b5650dbbfa994b400ca96853 100644 --- a/djangoldp/tests/tests_update.py +++ b/djangoldp/tests/tests_update.py @@ -5,7 +5,7 @@ from djangoldp.serializers import LDPSerializer from djangoldp.tests.models import Skill, JobOffer, Thread, Message -class Serializer(TestCase): +class Update(TestCase): def test_update(self): skill = Skill.objects.create(title="to drop", obligatoire="obligatoire") diff --git a/djangoldp/tests/urls.py b/djangoldp/tests/urls.py index 0066129ba1d57ef46610773bbcc237d840232f30..392b0e2b7764a2fe37fa87e1b47f3ea9916f66f7 100644 --- a/djangoldp/tests/urls.py +++ b/djangoldp/tests/urls.py @@ -1,6 +1,6 @@ from django.conf import settings -from djangoldp.tests.models import Skill, JobOffer, Message, Thread +from djangoldp.tests.models import Skill, JobOffer, Message, Thread, Dummy, LDPDummy from djangoldp.views import LDPViewSet from django.conf.urls import url @@ -11,4 +11,6 @@ urlpatterns = [ 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'^ldp-dummys/', LDPViewSet.urls(model=LDPDummy, permission_classes=[])), ] \ No newline at end of file