diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py index 453363132a9d8d9e7f7b7452fbd4b36f0300d6f5..530cc1f816fa889b484441a301e346290afd6edb 100644 --- a/djangoldp/serializers.py +++ b/djangoldp/serializers.py @@ -1,42 +1,62 @@ -import json from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import get_resolver from django.utils.datastructures import MultiValueDictKeyError +from guardian.shortcuts import get_perms +from rest_framework.fields import empty from rest_framework.relations import HyperlinkedRelatedField, ManyRelatedField, MANY_RELATION_KWARGS from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer -from rest_framework.utils.serializer_helpers import ReturnDict from rest_framework.utils.field_mapping import get_nested_relation_kwargs -from guardian.shortcuts import get_perms +from rest_framework.utils.serializer_helpers import ReturnDict + class LDListMixin: def to_internal_value(self, data): - data = json.loads(data) + # data = json.loads(data) + try: + data = data['ldp:contains'] + except TypeError: + pass if isinstance(data, dict): data = [data] - return [self.child_relation.to_internal_value(item['@id']) for item in data] + return [self.child.to_internal_value(item) for item in data] + def to_representation(self, value): return {'@id': self.id, 'ldp:contains': super().to_representation(value)} + def get_attribute(self, instance): parent_id_field = self.parent.fields[self.parent.url_field_name] context = self.parent.context parent_id = parent_id_field.get_url(instance, parent_id_field.view_name, context['request'], context['format']) - self.id = parent_id + self.field_name+"/" + self.id = parent_id + self.field_name + "/" return super().get_attribute(instance) + class ContainerSerializer(LDListMixin, ListSerializer): - id='' + id = '' + @property def data(self): return ReturnDict(super(ListSerializer, self).data, serializer=self) + def create(self, validated_data): + return super().create(validated_data) + + def to_internal_value(self, data): + try: + return super().to_internal_value(data['@id']) + except: + return super().to_internal_value(data) + + class ManyJsonLdRelatedField(LDListMixin, ManyRelatedField): pass + class JsonLdField(HyperlinkedRelatedField): def __init__(self, view_name=None, **kwargs): super().__init__(view_name, **kwargs) self.get_lookup_args() - + def get_lookup_args(self): try: lookup_field = get_resolver().reverse_dict[self.view_name][0][0][1][0] @@ -45,19 +65,20 @@ class JsonLdField(HyperlinkedRelatedField): except MultiValueDictKeyError: pass + class JsonLdRelatedField(JsonLdField): def to_representation(self, value): try: return {'@id': super().to_representation(value)} except ImproperlyConfigured: return value.pk - + def to_internal_value(self, data): try: return super().to_internal_value(data['@id']) except: return super().to_internal_value(data) - + @classmethod def many_init(cls, *args, **kwargs): list_kwargs = {'child_relation': cls(*args, **kwargs)} @@ -66,36 +87,52 @@ class JsonLdRelatedField(JsonLdField): list_kwargs[key] = kwargs[key] return ManyJsonLdRelatedField(**list_kwargs) + class JsonLdIdentityField(JsonLdField): def __init__(self, view_name=None, **kwargs): kwargs['read_only'] = True kwargs['source'] = '*' super().__init__(view_name, **kwargs) - + def use_pk_only_optimization(self): return False + def to_internal_value(self, data): + try: + return super().to_internal_value(data['@id']) + except: + return super().to_internal_value(data) + + class LDPSerializer(HyperlinkedModelSerializer): url_field_name = "@id" serializer_related_field = JsonLdRelatedField serializer_url_field = JsonLdIdentityField - + def get_default_field_names(self, declared_fields, model_info): try: fields = list(self.Meta.model._meta.serializer_fields) - except: + except AttributeError: fields = super().get_default_field_names(declared_fields, model_info) + try: + fields.remove(self.Meta.model._meta.auto_author) + except ValueError: + pass + except AttributeError: + pass return fields + list(getattr(self.Meta, 'extra_fields', [])) - + def to_representation(self, obj): data = super().to_representation(obj) if hasattr(obj._meta, 'rdf_type'): data['@type'] = obj._meta.rdf_type - data['permissions'] = [{'mode': {'@type': name.split('_')[0]}} for name in get_perms(self.context['request'].user, obj)] + data['permissions'] = [{'mode': {'@type': name.split('_')[0]}} for name in + get_perms(self.context['request'].user, obj)] return data - + def build_nested_field(self, field_name, relation_info, nested_depth): class NestedLDPSerializer(self.__class__): + class Meta: model = relation_info.related_model depth = nested_depth - 1 @@ -104,10 +141,32 @@ class LDPSerializer(HyperlinkedModelSerializer): except AttributeError: fields = '__all__' - - return NestedLDPSerializer, get_nested_relation_kwargs(relation_info) - + def to_internal_value(self, data): + model = self.Meta.model + return self.serializer_related_field( + view_name='{}-detail'.format(model._meta.object_name.lower()), + queryset=model.objects.all()).to_internal_value(data) + + kwargs = get_nested_relation_kwargs(relation_info) + kwargs['read_only'] = False + kwargs['required'] = False + return NestedLDPSerializer, kwargs + @classmethod def many_init(cls, *args, **kwargs): kwargs['child'] = cls() return ContainerSerializer(*args, **kwargs) + + def create(self, validated_data): + nested_fields = [] + nested_fields_name = list(filter(lambda key: isinstance(validated_data[key], list), validated_data)) + for field_name in nested_fields_name: + nested_fields.append((field_name, validated_data.pop(field_name))) + + obj = self.Meta.model.objects.create(**validated_data) + + for (field_name, data) in nested_fields: + for item in data: + getattr(obj, field_name).add(item) + + return obj diff --git a/djangoldp/tests.py b/djangoldp/tests.py deleted file mode 100644 index 7ce503c2dd97ba78597f6ff6e4393132753573f6..0000000000000000000000000000000000000000 --- a/djangoldp/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py new file mode 100644 index 0000000000000000000000000000000000000000..d8e0e76be8cc145c7dcc45635a4a342037dcc9a9 --- /dev/null +++ b/djangoldp/tests/models.py @@ -0,0 +1,12 @@ +from django.conf import settings +from django.db import models + + +class Skill(models.Model): + title = models.CharField(max_length=255, blank=True, null=True) + + +class JobOffer(models.Model): + title = models.CharField(max_length=255, blank=True, null=True) + skills = models.ManyToManyField(Skill, blank=True) + diff --git a/djangoldp/tests/runner.py b/djangoldp/tests/runner.py new file mode 100644 index 0000000000000000000000000000000000000000..8e04314473e2e7b8898ed9f3fa004daa303dc121 --- /dev/null +++ b/djangoldp/tests/runner.py @@ -0,0 +1,29 @@ +import django +import sys +from django.conf import settings + +settings.configure(DEBUG=True, + DATABASES={ + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + } + }, + ROOT_URLCONF='djangoldp.tests.urls', + INSTALLED_APPS=('django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.admin', + 'guardian', + 'djangoldp', + 'djangoldp.tests', + )) + + +django.setup() +from django.test.runner import DiscoverRunner + +test_runner = DiscoverRunner(verbosity=1) + +failures = test_runner.run_tests(['djangoldp.tests.tests']) +if failures: + sys.exit(failures) diff --git a/djangoldp/tests/tests.py b/djangoldp/tests/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..647b26c367aa8bdcdfce3f581a9f937f1d15c9da --- /dev/null +++ b/djangoldp/tests/tests.py @@ -0,0 +1,45 @@ +from django.test import TestCase + +from djangoldp.serializers import LDPSerializer +from djangoldp.tests.models import Skill, JobOffer + + +class Serializer(TestCase): + + def test_container_serializer_save(self): + skill1 = Skill.objects.create(title="skill1") + skill2 = Skill.objects.create(title="skill2") + 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)}, + ]} + } + + meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")} + + meta_class = type('Meta', (), meta_args) + serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class}) + serializer = serializer_class(data=job) + serializer.is_valid() + result = serializer.save() + + self.assertEquals(result.title, "job test") + self.assertIs(result.skills.count(), 2) + + def test_save_without_nested_fields(self): + skill1 = Skill.objects.create(title="skill1") + skill2 = Skill.objects.create(title="skill2") + job = {"title": "job test"} + + meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")} + + meta_class = type('Meta', (), meta_args) + serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class}) + serializer = serializer_class(data=job) + serializer.is_valid() + result = serializer.save() + + self.assertEquals(result.title, "job test") + self.assertIs(result.skills.count(), 0) diff --git a/djangoldp/tests/urls.py b/djangoldp/tests/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..5982784daeaeaf8c7b1f8e59f784632b7ae95cbe --- /dev/null +++ b/djangoldp/tests/urls.py @@ -0,0 +1,9 @@ +from djangoldp.tests.models import Skill, JobOffer +from djangoldp.views import LDPViewSet +from django.conf.urls import url + + +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=())), +] \ No newline at end of file diff --git a/djangoldp/views.py b/djangoldp/views.py index 2412a383a6e61355ac5cf30dccbb08b3c39745f2..49780c288ca183a93148690f57ed41ee40513f0b 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -137,6 +137,9 @@ class LDPViewSet(LDPViewSetGenerator): response["Accept-Post"] = "application/ld+json" return response + def update(self, request, *args, **kwargs): + response = super().update(request, *args, **kwargs) + return response class LDPNestedViewSet(LDPViewSet): """A special case of LDPViewSet serving objects of a relation of a given object (e.g. members of a group, or skills of a user)"""