Skip to content
Snippets Groups Projects
Commit 9b84c80c authored by Thibaud Duquennoy's avatar Thibaud Duquennoy
Browse files

Merge branch 'Url-ID-field' of...

Merge branch 'Url-ID-field' of git.happy-dev.fr:startinblox/djangoldp-packages/djangoldp into Url-ID-field

# Conflicts:
#	djangoldp/models.py
#	djangoldp/serializers.py
#	djangoldp/tests/tests_save.py
parents e0990f24 2d5abb4a
No related branches found
No related tags found
1 merge request!25Url id field
...@@ -8,7 +8,8 @@ stages: ...@@ -8,7 +8,8 @@ stages:
test: test:
stage: test stage: test
script: script:
- echo 'Make your tests here !' - pip install .[dev]
- python -m unittest djangoldp.tests.runner
except: except:
- master - master
tags: tags:
......
...@@ -27,11 +27,14 @@ django-admin startproject myldpserver ...@@ -27,11 +27,14 @@ django-admin startproject myldpserver
``` ```
3. Create your django model inside a file myldpserver/myldpserver/models.py 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) name = models.CharField(max_length=255)
deadline = models.DateTimeField() deadline = models.DateTimeField()
......
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.urls import get_resolver
from rest_framework import fields from rest_framework import fields
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
if path is None:
path = "{}s".format(instance._meta.object_name.lower())
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 LDPUrlField (fields.URLField): class LDPUrlField (fields.URLField):
def to_representation(self, value): def to_representation(self, value):
str = super(LDPUrlField, self).to_representation(value) str = super(LDPUrlField, self).to_representation(value)
return {'@id': str} return {'@id': str}
class LDPSource(models.Model): class LDPSource(models.Model):
container = models.URLField() container = models.URLField()
federation = models.CharField(max_length=255) federation = models.CharField(max_length=255)
class Meta: class Meta:
rdf_type = 'sib:source' rdf_type = 'sib:source'
ordering = ('federation',) ordering = ('federation',)
...@@ -18,7 +88,7 @@ class LDPSource(models.Model): ...@@ -18,7 +88,7 @@ class LDPSource(models.Model):
('view_source', 'acl:Read'), ('view_source', 'acl:Read'),
('control_source', 'acl:Control'), ('control_source', 'acl:Control'),
) )
def __str__(self): def __str__(self):
return "{}: {}".format(self.federation, self.container) return "{}: {}".format(self.federation, self.container)
...@@ -30,6 +100,7 @@ class LDNotification(models.Model): ...@@ -30,6 +100,7 @@ class LDNotification(models.Model):
type = models.CharField(max_length=255) type = models.CharField(max_length=255)
summary = models.TextField() summary = models.TextField()
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
class Meta: class Meta:
permissions = ( permissions = (
('view_todo', 'Read'), ('view_todo', 'Read'),
......
from collections import OrderedDict, Mapping
from urllib import parse
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import get_resolver from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.urlresolvers import get_resolver, resolve, get_script_prefix, Resolver404
from django.utils.datastructures import MultiValueDictKeyError from django.utils.datastructures import MultiValueDictKeyError
from django.utils.encoding import uri_to_iri
from guardian.shortcuts import get_perms from guardian.shortcuts import get_perms
from rest_framework.fields import empty from rest_framework.exceptions import ValidationError
from rest_framework.fields import SkipField, empty
from rest_framework.fields import get_error_detail, set_value
from rest_framework.relations import HyperlinkedRelatedField, ManyRelatedField, MANY_RELATION_KWARGS from rest_framework.relations import HyperlinkedRelatedField, ManyRelatedField, MANY_RELATION_KWARGS
from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer
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
from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer, ModelSerializer from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer, ModelSerializer
from rest_framework.utils.field_mapping import get_nested_relation_kwargs from rest_framework.utils.field_mapping import get_nested_relation_kwargs
from rest_framework.utils.serializer_helpers import ReturnDict from rest_framework.utils.serializer_helpers import ReturnDict
...@@ -13,17 +27,19 @@ from djangoldp import models ...@@ -13,17 +27,19 @@ from djangoldp import models
class LDListMixin: class LDListMixin:
def to_internal_value(self, data): def to_internal_value(self, data):
# data = json.loads(data)
try: try:
data = data['ldp:contains'] data = data['ldp:contains']
except TypeError: except (TypeError, KeyError):
pass pass
if isinstance(data, dict): if isinstance(data, dict):
data = [data] data = [data]
if isinstance(data, str) and str.startswith("http"):
data = [{'@id': data}]
return [self.child.to_internal_value(item) for item in data] return [self.child.to_internal_value(item) for item in data]
def to_representation(self, value): def to_representation(self, value):
return {'@id': self.id, '@type': 'ldp:Container', 'ldp:contains': super().to_representation(value)} return {'@id': self.id, '@type': 'ldp:Container', 'ldp:contains': super().to_representation(value)}
def get_attribute(self, instance): def get_attribute(self, instance):
parent_id_field = self.parent.fields[self.parent.url_field_name] parent_id_field = self.parent.fields[self.parent.url_field_name]
context = self.parent.context context = self.parent.context
...@@ -31,6 +47,41 @@ class LDListMixin: ...@@ -31,6 +47,41 @@ class LDListMixin:
self.id = parent_id + self.field_name + "/" self.id = parent_id + self.field_name + "/"
return super().get_attribute(instance) return super().get_attribute(instance)
def get_value(self, dictionary):
try:
object_list = dictionary["@graph"]
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))
except (KeyError, TypeError):
pass
try:
list = list['ldp:contains']
except (KeyError, TypeError):
pass
if isinstance(list, dict):
list = [list]
ret = []
for item in list:
full_item = None
try:
full_item = next(filter(lambda o: item['@id'] == o['@id'], object_list))
except StopIteration:
pass
if full_item is None:
ret.append(item)
else:
ret.append(full_item)
return ret
except KeyError:
return super().get_value(dictionary)
class ContainerSerializer(LDListMixin, ListSerializer): class ContainerSerializer(LDListMixin, ListSerializer):
id = '' id = ''
...@@ -45,7 +96,7 @@ class ContainerSerializer(LDListMixin, ListSerializer): ...@@ -45,7 +96,7 @@ class ContainerSerializer(LDListMixin, ListSerializer):
def to_internal_value(self, data): def to_internal_value(self, data):
try: try:
return super().to_internal_value(data['@id']) return super().to_internal_value(data['@id'])
except: except (KeyError, TypeError):
return super().to_internal_value(data) return super().to_internal_value(data)
...@@ -66,6 +117,12 @@ class JsonLdField(HyperlinkedRelatedField): ...@@ -66,6 +117,12 @@ class JsonLdField(HyperlinkedRelatedField):
except MultiValueDictKeyError: except MultiValueDictKeyError:
pass pass
def to_internal_value(self, data):
return super().to_internal_value(data)
def get_value(self, dictionary):
return super().get_value(dictionary)
class JsonLdRelatedField(JsonLdField): class JsonLdRelatedField(JsonLdField):
def to_representation(self, value): def to_representation(self, value):
...@@ -77,7 +134,7 @@ class JsonLdRelatedField(JsonLdField): ...@@ -77,7 +134,7 @@ class JsonLdRelatedField(JsonLdField):
def to_internal_value(self, data): def to_internal_value(self, data):
try: try:
return super().to_internal_value(data['@id']) return super().to_internal_value(data['@id'])
except: except (KeyError, TypeError):
return super().to_internal_value(data) return super().to_internal_value(data)
@classmethod @classmethod
...@@ -101,17 +158,24 @@ class JsonLdIdentityField(JsonLdField): ...@@ -101,17 +158,24 @@ class JsonLdIdentityField(JsonLdField):
def to_internal_value(self, data): def to_internal_value(self, data):
try: try:
return super().to_internal_value(data['@id']) return super().to_internal_value(data['@id'])
except: except KeyError:
return super().to_internal_value(data) return super().to_internal_value(data)
def get_value(self, dictionary):
return super().get_value(dictionary)
class LDPSerializer(HyperlinkedModelSerializer): class LDPSerializer(HyperlinkedModelSerializer):
url_field_name = "@id" url_field_name = "@id"
serializer_related_field = JsonLdRelatedField serializer_related_field = JsonLdRelatedField
serializer_url_field = JsonLdIdentityField serializer_url_field = JsonLdIdentityField
ModelSerializer.serializer_field_mapping [django_models.URLField] = models.LDPUrlField ModelSerializer.serializer_field_mapping [django_models.URLField] = models.LDPUrlField
@property
def data(self):
return super().data
def get_default_field_names(self, declared_fields, model_info): def get_default_field_names(self, declared_fields, model_info):
try: try:
fields = list(self.Meta.model._meta.serializer_fields) fields = list(self.Meta.model._meta.serializer_fields)
...@@ -134,6 +198,27 @@ class LDPSerializer(HyperlinkedModelSerializer): ...@@ -134,6 +198,27 @@ class LDPSerializer(HyperlinkedModelSerializer):
get_perms(self.context['request'].user, obj)] get_perms(self.context['request'].user, obj)]
return data return data
def build_standard_field(self, field_name, model_field):
class JSonLDStandardField:
parent_view_name = None
def __init__(self, **kwargs):
self.parent_view_name = kwargs.pop('parent_view_name')
super().__init__(**kwargs)
def get_value(self, dictionary):
try:
object_list = dictionary["@graph"]
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)
field_class, field_kwargs = super().build_standard_field(field_name, model_field)
field_kwargs['parent_view_name'] = '{}-list'.format(model_field.model._meta.object_name.lower())
return type(field_class.__name__ + 'Valued', (JSonLDStandardField, field_class), {}), field_kwargs
def build_nested_field(self, field_name, relation_info, nested_depth): def build_nested_field(self, field_name, relation_info, nested_depth):
class NestedLDPSerializer(self.__class__): class NestedLDPSerializer(self.__class__):
...@@ -146,10 +231,56 @@ class LDPSerializer(HyperlinkedModelSerializer): ...@@ -146,10 +231,56 @@ class LDPSerializer(HyperlinkedModelSerializer):
fields = '__all__' fields = '__all__'
def to_internal_value(self, data): def to_internal_value(self, data):
model = self.Meta.model if self.url_field_name in data:
return self.serializer_related_field( if not isinstance(data, Mapping):
view_name='{}-detail'.format(model._meta.object_name.lower()), message = self.error_messages['invalid'].format(
queryset=model.objects.all()).to_internal_value(data) datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='invalid')
ret = OrderedDict()
errors = OrderedDict()
fields = list(filter(lambda x: x.field_name in data, self._writable_fields))
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
uri = data[self.url_field_name]
http_prefix = uri.startswith(('http:', 'https:'))
if http_prefix:
uri = parse.urlparse(uri).path
prefix = get_script_prefix()
if uri.startswith(prefix):
uri = '/' + uri[len(prefix):]
try:
match = resolve(uri_to_iri(uri))
ret['pk'] = match.kwargs['pk']
except Resolver404:
pass
return ret
else:
return super().to_internal_value(data)
kwargs = get_nested_relation_kwargs(relation_info) kwargs = get_nested_relation_kwargs(relation_info)
kwargs['read_only'] = False kwargs['read_only'] = False
...@@ -158,19 +289,96 @@ class LDPSerializer(HyperlinkedModelSerializer): ...@@ -158,19 +289,96 @@ class LDPSerializer(HyperlinkedModelSerializer):
@classmethod @classmethod
def many_init(cls, *args, **kwargs): def many_init(cls, *args, **kwargs):
kwargs['child'] = cls() kwargs['child'] = cls(**kwargs)
try:
cls.Meta.depth = kwargs['context']['view'].many_depth
except KeyError:
pass
return ContainerSerializer(*args, **kwargs) return ContainerSerializer(*args, **kwargs)
def get_value(self, dictionary):
try:
object_list = dictionary["@graph"]
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:
return empty
try:
full_item = next(filter(lambda o: item['@id'] == o['@id'], object_list))
except StopIteration:
pass
if full_item is None:
return item
else:
return full_item
except KeyError:
return super().get_value(dictionary)
def create(self, validated_data): def create(self, validated_data):
return self.internal_create(validated_data, model=self.Meta.model)
def internal_create(self, validated_data, model):
nested_fields = [] nested_fields = []
nested_fields_name = list(filter(lambda key: isinstance(validated_data[key], list), validated_data)) nested_fields_name = list(filter(lambda key: isinstance(validated_data[key], list), validated_data))
for field_name in nested_fields_name: for field_name in nested_fields_name:
nested_fields.append((field_name, validated_data.pop(field_name))) nested_fields.append((field_name, validated_data.pop(field_name)))
obj = self.Meta.model.objects.create(**validated_data) instance = model.objects.create(**validated_data)
self.save_or_update_nested_list(instance, nested_fields)
return instance
def update(self, instance, 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)))
for attr, value in validated_data.items():
if isinstance(value, dict):
manager = getattr(instance, attr)
if 'pk' in value:
oldObj = manager._meta.model.objects.get(pk=value['pk'])
value = self.update(instance=oldObj, validated_data=value)
else:
value = self.internal_create(validated_data=value, model=manager._meta.model)
setattr(instance, attr, value)
instance.save()
self.save_or_update_nested_list(instance, nested_fields)
return instance
def save_or_update_nested_list(self, instance, nested_fields):
for (field_name, data) in nested_fields: for (field_name, data) in nested_fields:
for item in data: manager = getattr(instance, field_name)
getattr(obj, field_name).add(item)
return obj item_pk_to_keep = list(map(lambda e: int(e['pk']), filter(lambda x: 'pk' in x, data)))
for item in list(manager.all()):
if not item.pk 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'])
savedItem = self.update(instance=oldObj, validated_data=item)
else:
rel = getattr(instance._meta.model, field_name).rel
try:
if rel.related_model == manager.model:
reverse_id = rel.remote_field.attname
item[reverse_id] = instance.pk
except AttributeError:
pass
savedItem = self.internal_create(validated_data=item, model=manager.model)
if getattr(manager, 'through', None) is not None and manager.through._meta.auto_created:
manager.add(savedItem)
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from djangoldp.models import Model
class Skill(models.Model): class Skill(models.Model):
title = models.CharField(max_length=255, blank=True, null=True) title = models.CharField(max_length=255, blank=True, null=True)
obligatoire = models.CharField(max_length=255)
class JobOffer(models.Model): class JobOffer(models.Model):
title = models.CharField(max_length=255, blank=True, null=True) title = models.CharField(max_length=255, blank=True, null=True)
skills = models.ManyToManyField(Skill, blank=True) skills = models.ManyToManyField(Skill, blank=True)
class Thread(models.Model):
description = models.CharField(max_length=255, blank=True, null=True)
author_user = models.ForeignKey(settings.AUTH_USER_MODEL)
class Message(models.Model):
text = models.CharField(max_length=255, blank=True, null=True)
thread = models.ForeignKey(Thread, on_delete=models.DO_NOTHING)
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)
...@@ -24,6 +24,12 @@ from django.test.runner import DiscoverRunner ...@@ -24,6 +24,12 @@ from django.test.runner import DiscoverRunner
test_runner = DiscoverRunner(verbosity=1) test_runner = DiscoverRunner(verbosity=1)
failures = test_runner.run_tests(['djangoldp.tests.tests']) failures = test_runner.run_tests([
'djangoldp.tests.tests_ldp_model',
'djangoldp.tests.tests_save',
'djangoldp.tests.tests_user_permissions',
'djangoldp.tests.tests_anonymous_permissions',
'djangoldp.tests.tests_update'])
if failures: if failures:
sys.exit(failures) sys.exit(failures)
from django.test import TestCase from django.contrib.auth.models import AnonymousUser
from django.test import TestCase, RequestFactory
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)
from django.test import TestCase, Client, RequestFactory
from djangoldp.views import LDPViewSet
from djangoldp.permissions import AnonymousReadOnly from djangoldp.permissions import AnonymousReadOnly
from djangoldp.tests.models import JobOffer
from django.contrib.auth.models import AnonymousUser, User from djangoldp.views import LDPViewSet
from djangoldp_joboffer.models import JobOffer
class TestUserPermissions (TestCase):
def setUp(self):
self.factory = RequestFactory()
# self.c = Client()
self.user = User.objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion')
def tearDown(self):
self.user.delete()
def test_get_with_user(self):
request = self.factory.get('/job-offers/')
request.user = self.user
my_view = LDPViewSet.as_view({'get': 'list'}, model=JobOffer, nested_fields=["skills"], permission_classes=[AnonymousReadOnly])
response = my_view(request)
self.assertEqual(response.status_code, 200)
def test_request_options_create_with_user(self):
request = self.factory.options('/job-offers/')
request.user = self.user
my_view = LDPViewSet.as_view({'options': 'create'}, model=JobOffer, nested_fields=["skills"], permission_classes=[AnonymousReadOnly])
response = my_view(request)
self.assertEqual(response.status_code, 201)
def test_request_options_update_with_user(self):
request = self.factory.options('/job-offers/')
request.user = self.user
my_view = LDPViewSet.as_view({'options': 'update'}, model=JobOffer, nested_fields=["skills"], permission_classes=[AnonymousReadOnly])
response = my_view(request)
self.assertEqual(response.status_code, 201)
class TestAnonymousUserPermissions (TestCase): class TestAnonymousUserPermissions(TestCase):
def setUp(self): def setUp(self):
self.factory = RequestFactory() self.factory = RequestFactory()
# self.c = Client() # self.c = Client()
self.user = AnonymousUser self.user = AnonymousUser
def test_get_request_with_anonymousUser(self): def test_get_request_with_anonymousUser(self):
...@@ -118,5 +41,3 @@ class TestAnonymousUserPermissions (TestCase): ...@@ -118,5 +41,3 @@ class TestAnonymousUserPermissions (TestCase):
permission_classes=[AnonymousReadOnly]) permission_classes=[AnonymousReadOnly])
response = my_view(request) response = my_view(request)
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
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("/ldpdummys/", dummy.get_container_id())
self.assertEquals("/ldpdummys/{}".format(dummy.pk), dummy.get_absolute_url())
self.assertEquals("/ldpdummys/", Model.container_id(dummy))
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))
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())
from django.test import TestCase
from djangoldp.serializers import LDPSerializer
from djangoldp.tests.models import Skill, JobOffer
class Save(TestCase):
def test_save_m2m(self):
skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire")
skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire")
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"},
]}
}
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)
self.assertEquals(result.skills.all()[0].title, "skill1") # no change
self.assertEquals(result.skills.all()[1].title, "skill2 UP") # title updated
def test_save_without_nested_fields(self):
skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire")
skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire")
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)
from django.contrib.auth.models import User
from django.test import TestCase
from djangoldp.serializers import LDPSerializer
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")
job1 = JobOffer.objects.create(title="job test")
job1.skills.add(skill)
job = {"@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk),
"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"},
]}
}
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, instance=job1)
serializer.is_valid()
result = serializer.save()
self.assertEquals(result.title, "job test updated")
self.assertIs(result.skills.count(), 3)
skills = result.skills.all().order_by('title')
self.assertEquals(skills[0].title, "new skill") # new skill
self.assertEquals(skills[1].title, "skill1") # no change
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")
job1.skills.add(skill)
job = {"@graph":
[
{
"@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk),
"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": "_.123"},
]}
},
{
"@id": "_.123",
"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"
}
]
}
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, instance=job1)
serializer.is_valid()
result = serializer.save()
skills = result.skills.all().order_by('title')
self.assertEquals(result.title, "job test updated")
self.assertIs(result.skills.count(), 3)
self.assertEquals(skills[0].title, "new skill") # new skill
self.assertEquals(skills[1].title, "skill1") # no change
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")
job1.skills.add(skill)
job = {"@graph":
[
{
"@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk),
"title": "job test updated",
"skills": {
"@id": "https://happy-dev.fr/job-offers/{}/skills/".format(job1.pk)
}
},
{
"@id": "_.123",
"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/job-offers/{}/skills/".format(job1.pk),
"ldp:contains": [
{"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)},
{"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk)},
{"@id": "_.123"},
]
}
]
}
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, instance=job1)
serializer.is_valid()
result = serializer.save()
skills = result.skills.all().order_by('title')
self.assertEquals(result.title, "job test updated")
self.assertIs(result.skills.count(), 3)
self.assertEquals(skills[0].title, "new skill") # new skill
self.assertEquals(skills[1].title, "skill1") # no change
self.assertEquals(skills[2].title, "skill2 UP") # title updated
self.assertEquals(skill, skill._meta.model.objects.get(pk=skill.pk)) # title updated
def test_update_list_with_reverse_relation(self):
user1 = User.objects.create()
thread = Thread.objects.create(description="Thread 1", author_user=user1)
message1 = Message.objects.create(text="Message 1", thread=thread, author_user=user1)
message2 = Message.objects.create(text="Message 2", thread=thread, author_user=user1)
json = {"@graph": [
{
"@id": "https://happy-dev.fr/messages/{}/".format(message1.pk),
"text": "Message 1 UP"
},
{
"@id": "https://happy-dev.fr/messages/{}/".format(message2.pk),
"text": "Message 2 UP"
},
{
'@id': "https://happy-dev.fr/threads/{}/".format(thread.pk),
'description': "Thread 1 UP",
"message_set": [
{"@id": "https://happy-dev.fr/messages/{}/".format(message1.pk)},
{"@id": "https://happy-dev.fr/messages/{}/".format(message2.pk)},
]
}
]
}
meta_args = {'model': Thread, 'depth': 1, 'fields': ("@id", "description", "message_set")}
meta_class = type('Meta', (), meta_args)
serializer_class = type(LDPSerializer)('ThreadSerializer', (LDPSerializer,), {'Meta': meta_class})
serializer = serializer_class(data=json, instance=thread)
serializer.is_valid()
result = serializer.save()
messages = result.message_set.all().order_by('text')
self.assertEquals(result.description, "Thread 1 UP")
self.assertIs(result.message_set.count(), 2)
self.assertEquals(messages[0].text, "Message 1 UP")
self.assertEquals(messages[1].text, "Message 2 UP")
def test_add_new_element_with_foreign_key_id(self):
user1 = User.objects.create()
thread = Thread.objects.create(description="Thread 1", author_user=user1)
message1 = Message.objects.create(text="Message 1", thread=thread, author_user=user1)
message2 = Message.objects.create(text="Message 2", thread=thread, author_user=user1)
json = {"@graph": [
{
"@id": "https://happy-dev.fr/messages/{}/".format(message1.pk),
"text": "Message 1 UP",
"author_user": {
'@id': "https://happy-dev.fr/users/{}/".format(user1.pk)
}
},
{
"@id": "https://happy-dev.fr/messages/{}/".format(message2.pk),
"text": "Message 2 UP",
"author_user": {
'@id': "https://happy-dev.fr/users/{}/".format(user1.pk)
}
},
{
"@id": "_:b1",
"text": "Message 3 NEW",
"author_user": {
'@id': "https://happy-dev.fr/users/{}/".format(user1.pk)
}
},
{
'@id': "https://happy-dev.fr/threads/{}".format(thread.pk),
"author_user": {
'@id': "https://happy-dev.fr/users/{}/".format(user1.pk)
},
'description': "Thread 1 UP",
'message_set': {
"@id": "https://happy-dev.fr/threads/{}/message_set".format(thread.pk)
}
},
{
'@id': "https://happy-dev.fr/threads/{}/message_set".format(thread.pk),
"ldp:contains": [
{"@id": "https://happy-dev.fr/messages/{}/".format(message1.pk)},
{"@id": "https://happy-dev.fr/messages/{}/".format(message2.pk)},
{"@id": "_:b1"}
]
}
]
}
meta_args = {'model': Thread, 'depth': 1, 'fields': ("@id", "description", "message_set")}
meta_class = type('Meta', (), meta_args)
serializer_class = type(LDPSerializer)('ThreadSerializer', (LDPSerializer,), {'Meta': meta_class})
serializer = serializer_class(data=json, instance=thread)
serializer.is_valid()
result = serializer.save()
messages = result.message_set.all().order_by('text')
self.assertEquals(result.description, "Thread 1 UP")
self.assertIs(result.message_set.count(), 3)
self.assertEquals(messages[0].text, "Message 1 UP")
self.assertEquals(messages[1].text, "Message 2 UP")
self.assertEquals(messages[2].text, "Message 3 NEW")
from django.contrib.auth.models import User
from django.test import TestCase, RequestFactory
from djangoldp.permissions import AnonymousReadOnly
from djangoldp.tests.models import JobOffer
from djangoldp.views import LDPViewSet
class TestUserPermissions(TestCase):
def setUp(self):
self.factory = RequestFactory()
# self.c = Client()
self.user = User.objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion')
def tearDown(self):
self.user.delete()
def test_get_with_user(self):
request = self.factory.get('/job-offers/')
request.user = self.user
my_view = LDPViewSet.as_view({'get': 'list'}, model=JobOffer, nested_fields=["skills"],
permission_classes=[AnonymousReadOnly])
response = my_view(request)
self.assertEqual(response.status_code, 200)
def test_request_options_create_with_user(self):
request = self.factory.options('/job-offers/')
request.user = self.user
my_view = LDPViewSet.as_view({'options': 'create'}, model=JobOffer, nested_fields=["skills"],
permission_classes=[AnonymousReadOnly])
response = my_view(request)
self.assertEqual(response.status_code, 201)
def test_request_options_update_with_user(self):
request = self.factory.options('/job-offers/')
request.user = self.user
my_view = LDPViewSet.as_view({'options': 'update'}, model=JobOffer, nested_fields=["skills"],
permission_classes=[AnonymousReadOnly])
response = my_view(request)
self.assertEqual(response.status_code, 201)
from djangoldp.tests.models import Skill, JobOffer from django.conf import settings
from djangoldp.tests.models import Skill, JobOffer, Message, Thread, Dummy, LDPDummy
from djangoldp.views import LDPViewSet from djangoldp.views import LDPViewSet
from django.conf.urls import url from django.conf.urls import url
...@@ -6,4 +8,9 @@ from django.conf.urls import url ...@@ -6,4 +8,9 @@ from django.conf.urls import url
urlpatterns = [ urlpatterns = [
url(r'^skills/', LDPViewSet.urls(model=Skill, permission_classes=[], fields=["@id", "title"], nested_fields=[])), 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'^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'^ldp-dummys/', LDPViewSet.urls(model=LDPDummy, permission_classes=[])),
] ]
\ No newline at end of file
...@@ -89,9 +89,10 @@ class LDPViewSet(LDPViewSetGenerator): ...@@ -89,9 +89,10 @@ class LDPViewSet(LDPViewSetGenerator):
"""An automatically generated viewset that serves models following the Linked Data Platform convention""" """An automatically generated viewset that serves models following the Linked Data Platform convention"""
fields = None fields = None
exclude = None exclude = None
depth = 0 depth = 1
many_depth = 0
renderer_classes = (JSONLDRenderer,) renderer_classes = (JSONLDRenderer,)
parser_classes =(JSONLDParser,) parser_classes = (JSONLDParser,)
authentication_classes = (NoCSRFAuthentication,) authentication_classes = (NoCSRFAuthentication,)
def __init__(self, **kwargs): def __init__(self, **kwargs):
...@@ -106,7 +107,9 @@ class LDPViewSet(LDPViewSetGenerator): ...@@ -106,7 +107,9 @@ class LDPViewSet(LDPViewSetGenerator):
def build_serializer(self): def build_serializer(self):
model_name = self.model._meta.object_name.lower() model_name = self.model._meta.object_name.lower()
lookup_field = get_resolver().reverse_dict[model_name + '-detail'][0][0][1][0] lookup_field = get_resolver().reverse_dict[model_name + '-detail'][0][0][1][0]
meta_args = {'model': self.model, 'extra_kwargs': {'@id': {'lookup_field': lookup_field}}, 'depth': self.depth, meta_args = {'model': self.model, 'extra_kwargs': {
'@id': {'lookup_field': lookup_field}},
'depth': self.depth,
'extra_fields': self.nested_fields} 'extra_fields': self.nested_fields}
if self.fields: if self.fields:
meta_args['fields'] = self.fields meta_args['fields'] = self.fields
...@@ -135,12 +138,16 @@ class LDPViewSet(LDPViewSetGenerator): ...@@ -135,12 +138,16 @@ class LDPViewSet(LDPViewSetGenerator):
response["Access-Control-Allow-Headers"] = "Content-Type, if-match" response["Access-Control-Allow-Headers"] = "Content-Type, if-match"
response["Access-Control-Allow-Credentials"] = 'true' response["Access-Control-Allow-Credentials"] = 'true'
response["Accept-Post"] = "application/ld+json" response["Accept-Post"] = "application/ld+json"
if response.status_code == 201 and '@id' in response.data:
response["Location"] = response.data['@id']
response["Accept-Post"] = "application/ld+json"
return response return response
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
response = super().update(request, *args, **kwargs) response = super().update(request, *args, **kwargs)
return response return response
class LDPNestedViewSet(LDPViewSet): 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)""" """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)"""
parent_model = None parent_model = None
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment