diff --git a/djangoldp/activities/services.py b/djangoldp/activities/services.py index 29de522ddf8a75a49dc85c22d04f81224f1b7ee0..15cbe2e44d21236349aa7ce3a517f9ac1dc246fe 100644 --- a/djangoldp/activities/services.py +++ b/djangoldp/activities/services.py @@ -1,16 +1,16 @@ import threading +import json +import requests from urllib.parse import urlparse -from django.conf import settings from django.contrib.auth import get_user_model from django.db.models.signals import post_save, post_delete, m2m_changed from django.dispatch import receiver +from django.conf import settings from rest_framework.utils import model_meta from djangoldp.models import Model, Follower from djangoldp.models import Activity as ActivityModel -from .objects import * -from .verbs import * import logging @@ -298,7 +298,7 @@ def check_m2m_for_backlinks(sender, instance, action, *args, **kwargs): for obj in query_set: condition = Model.is_external(obj) and getattr(obj, 'allow_create_backlink', False) if action == "post_add": - condition = condition and not obj.is_backlink + condition = condition and not getattr(obj, 'is_backlink', True) if condition: targets.append({ diff --git a/djangoldp/activities/verbs.py b/djangoldp/activities/verbs.py index 313b738f799021162e7f1208e78b4f886af6c404..55568d585a7e7ce535f216ac78d569c2b1f6a903 100644 --- a/djangoldp/activities/verbs.py +++ b/djangoldp/activities/verbs.py @@ -1,6 +1,5 @@ from copy import copy -from django.conf import settings from djangoldp.activities import errors from djangoldp.activities.objects import ALLOWED_TYPES, Object, Actor diff --git a/djangoldp/permissions.py b/djangoldp/permissions.py index 5d888fe7f596eca1436b7ead3a1f8ddd6d315fa0..4e1112068df585a163e992293407ed28bab88e76 100644 --- a/djangoldp/permissions.py +++ b/djangoldp/permissions.py @@ -1,7 +1,7 @@ from django.core.exceptions import PermissionDenied from django.db.models.base import ModelBase from rest_framework.permissions import DjangoObjectPermissions -from guardian.shortcuts import get_user_perms +from django.contrib.auth.models import _user_get_all_permissions class LDPPermissions(DjangoObjectPermissions): @@ -47,12 +47,10 @@ class LDPPermissions(DjangoObjectPermissions): perms = set() if obj is not None and not user.is_anonymous: - guardian_perms = get_user_perms(user, obj) + # get permissions from all backends and then remove model name from the permissions model_name = model._meta.model_name - - # remove model name from the permissions forbidden_string = "_" + model_name - perms = set([p.replace(forbidden_string, '') for p in guardian_perms]) + perms = set([p.replace(forbidden_string, '') for p in _user_get_all_permissions(user, obj)]) # apply anon, owner and auth permissions if user.is_anonymous: diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py index b10fe10956156ba773c0c28ae2555be49e3afce8..e0a62879307d8edda321cb18b61cf5af07fb5764 100644 --- a/djangoldp/serializers.py +++ b/djangoldp/serializers.py @@ -7,7 +7,8 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ValidationError as DjangoValidationError -from django.core.urlresolvers import get_resolver, resolve, get_script_prefix, Resolver404 +from django.urls import resolve, Resolver404, get_script_prefix +from django.urls import get_resolver from django.db.models import QuerySet from django.utils.datastructures import MultiValueDictKeyError from django.utils.encoding import uri_to_iri @@ -514,6 +515,7 @@ class LDPSerializer(HyperlinkedModelSerializer): return ret def get_value(self, dictionary): + '''overrides get_value to handle @graph key''' try: object_list = dictionary["@graph"] if self.parent.instance is None: @@ -549,10 +551,10 @@ class LDPSerializer(HyperlinkedModelSerializer): return instance def attach_related_object(self, instance, validated_data): + '''adds m2m relations included in validated_data to the instance''' model_class = self.Meta.model info = model_meta.get_field_info(model_class) - 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 is None: rel = getattr(instance._meta.model, field_name).rel @@ -561,7 +563,7 @@ class LDPSerializer(HyperlinkedModelSerializer): getattr(instance, field_name).add(related) def internal_create(self, validated_data, model): - validated_data = self.resolve_fk_instances(model, validated_data) + validated_data = self.resolve_fk_instances(model, validated_data, True) # build tuples list of nested_field keys and their values nested_fields = [] @@ -617,16 +619,18 @@ class LDPSerializer(HyperlinkedModelSerializer): return instance - def resolve_fk_instances(self, model, validated_data): - '''iterates over every dict object in validated_data and resolves them into instances (get or create)''' + def resolve_fk_instances(self, model, validated_data, create=False): + ''' + iterates over every dict object in validated_data and resolves them into instances (get or create) + :param model: the model being operated on + :param validated_data: the data passed to the serializer + :param create: set to True, foreign keys will be created if they do not exist + ''' nested_fk_fields_name = list(filter(lambda key: isinstance(validated_data[key], dict), validated_data)) for field_name in nested_fk_fields_name: field_dict = validated_data[field_name] - try: - field_model = getattr(model, field_name).field.rel.model - except: - # not fk - continue + field_model = model._meta.get_field(field_name).related_model + slug_field = Model.slug_field(field_model) sub_inst = None if 'urlid' in field_dict: @@ -646,7 +650,11 @@ class LDPSerializer(HyperlinkedModelSerializer): kwargs = {slug_field: field_dict[slug_field]} sub_inst = field_model.objects.get(**kwargs) if sub_inst is None: - sub_inst = self.internal_create(field_dict, field_model) + if create: + sub_inst = self.internal_create(field_dict, field_model) + else: + continue + validated_data[field_name] = sub_inst return validated_data @@ -664,13 +672,9 @@ class LDPSerializer(HyperlinkedModelSerializer): if relation_info.to_many: value = self.internal_create(validated_data=value, model=relation_info.related_model) else: - try: - reverse_attr_name = instance._meta.fields_map[attr].remote_field.name - many = False - except: - rel = list(filter(lambda field: field.name == attr, instance._meta.fields))[0].rel - many = rel.one_to_many - reverse_attr_name = rel.related_name + rel = instance._meta.get_field(attr) + reverse_attr_name = rel.remote_field.name + many = rel.one_to_many or rel.many_to_many if many: value[reverse_attr_name] = [instance] oldObj = rel.model.object.get(id=value['urlid']) diff --git a/djangoldp/tests/djangoldp_urls.py b/djangoldp/tests/djangoldp_urls.py index 4766fedf3b50f759a64578dea5c081adcef9aeb4..2d43fc3dbd7715089a2a5ecd29d69bc29aa0c5cc 100644 --- a/djangoldp/tests/djangoldp_urls.py +++ b/djangoldp/tests/djangoldp_urls.py @@ -1,16 +1,15 @@ -from django.conf import settings -from django.conf.urls import url, include +from django.conf.urls import re_path from djangoldp.permissions import LDPPermissions from djangoldp.tests.models import Skill, JobOffer, Message, Conversation, Dummy, PermissionlessDummy, Task, DateModel from djangoldp.views import LDPViewSet urlpatterns = [ - url(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[LDPPermissions], fields=["@id", "text", "conversation"], nested_fields=['conversation'])), - url(r'^conversations/', LDPViewSet.urls(model=Conversation, nested_fields=["message_set"], permission_classes=[LDPPermissions])), - url(r'^tasks/', LDPViewSet.urls(model=Task, permission_classes=[LDPPermissions])), - url(r'^dates/', LDPViewSet.urls(model=DateModel, permission_classes=[LDPPermissions])), - url(r'^dummys/', LDPViewSet.urls(model=Dummy, permission_classes=[LDPPermissions], lookup_field='slug',)), - url(r'^permissionless-dummys/', LDPViewSet.urls(model=PermissionlessDummy, permission_classes=[LDPPermissions], lookup_field='slug',)), + re_path(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[LDPPermissions], fields=["@id", "text", "conversation"], nested_fields=['conversation'])), + re_path(r'^conversations/', LDPViewSet.urls(model=Conversation, nested_fields=["message_set"], permission_classes=[LDPPermissions])), + re_path(r'^tasks/', LDPViewSet.urls(model=Task, permission_classes=[LDPPermissions])), + re_path(r'^dates/', LDPViewSet.urls(model=DateModel, permission_classes=[LDPPermissions])), + re_path(r'^dummys/', LDPViewSet.urls(model=Dummy, permission_classes=[LDPPermissions], lookup_field='slug',)), + re_path(r'^permissionless-dummys/', LDPViewSet.urls(model=PermissionlessDummy, permission_classes=[LDPPermissions], lookup_field='slug',)), ] diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py index a1bf7db49b1d4b0e3c6671eba4415b5dc8a2d262..eb4d11be75049641c0783a259c2ce5f2f4012e32 100644 --- a/djangoldp/tests/models.py +++ b/djangoldp/tests/models.py @@ -59,8 +59,9 @@ class JobOffer(Model): class Conversation(models.Model): description = models.CharField(max_length=255, blank=True, null=True) - author_user = models.ForeignKey(settings.AUTH_USER_MODEL) - peer_user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="peers_conv") + author_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING) + peer_user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="peers_conv", + on_delete=models.DO_NOTHING) class Meta(Model.Meta): anonymous_perms = ['view'] @@ -83,7 +84,7 @@ class Resource(Model): class UserProfile(Model): description = models.CharField(max_length=255, blank=True, null=True) - user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='userprofile') + user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='userprofile', on_delete=models.CASCADE) class Meta(Model.Meta): anonymous_perms = ['view'] @@ -95,7 +96,7 @@ class UserProfile(Model): class Message(models.Model): text = models.CharField(max_length=255, blank=True, null=True) conversation = models.ForeignKey(Conversation, on_delete=models.DO_NOTHING) - author_user = models.ForeignKey(settings.AUTH_USER_MODEL) + author_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING) class Meta(Model.Meta): anonymous_perms = ['view'] @@ -172,8 +173,9 @@ class Task(models.Model): class Post(Model): content = models.CharField(max_length=255) - author = models.ForeignKey(UserProfile, blank=True, null=True) - peer_user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="peers_post") + author = models.ForeignKey(UserProfile, blank=True, null=True, on_delete=models.SET_NULL) + peer_user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="peers_post", + on_delete=models.SET_NULL) class Meta(Model.Meta): auto_author = 'author' diff --git a/djangoldp/tests/settings_default.py b/djangoldp/tests/settings_default.py index db0e8609fe45891062ac76d64a632dcbabc78e84..6ecb12a9129cab2b3adf3b5e017daa9fc5b2c5f2 100644 --- a/djangoldp/tests/settings_default.py +++ b/djangoldp/tests/settings_default.py @@ -14,6 +14,8 @@ INSTALLED_APPS=('django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.admin', + 'django.contrib.messages', + 'django.contrib.staticfiles', 'guardian', 'djangoldp', 'djangoldp.tests', @@ -32,11 +34,37 @@ REST_FRAMEWORK = { AUTH_USER_MODEL='tests.User' ANONYMOUS_USER_NAME = None + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] AUTHENTICATION_BACKENDS=( 'django.contrib.auth.backends.ModelBackend', 'guardian.backends.ObjectPermissionBackend') ROOT_URLCONF='djangoldp.urls' +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + LDP_RDF_CONTEXT={ "@context": { "@vocab": "http://happy-dev.fr/owl/#", diff --git a/djangoldp/tests/tests_save.py b/djangoldp/tests/tests_save.py index 5ec9830d3e558f4950fabd9f36c0dd27d94424c4..d1ab7b30e1f4a4a8b0454f023239ca1ada46d257 100644 --- a/djangoldp/tests/tests_save.py +++ b/djangoldp/tests/tests_save.py @@ -328,6 +328,20 @@ class Save(TestCase): saved_post = Post.objects.get(pk=1) self.assertEqual(saved_post.urlid, "http://happy-dev.fr/posts/1/") + def test_save_invalid_nested_user(self): + body = { + '@id': "./", + 'content': "post update", + 'peer_user': {'none': None}, + '@context': { + "@vocab": "http://happy-dev.fr/owl/#", + } + } + + response = self.client.post('/posts/', data=json.dumps(body), + content_type='application/ld+json') + self.assertEqual(response.status_code, 400) + def test_nested_container_user_federated(self): project = Project.objects.create() body = { diff --git a/djangoldp/tests/tests_update.py b/djangoldp/tests/tests_update.py index 7157b72ef0b6b8c79f39b58b4df93953a663c87b..17d5702ceece7558d75adc4325ba363c105eaf43 100644 --- a/djangoldp/tests/tests_update.py +++ b/djangoldp/tests/tests_update.py @@ -572,6 +572,7 @@ class Update(TestCase): content_type='application/ld+json') self.assertEqual(response.status_code, 200) self.assertIn('userprofile', response.data) + self.assertIsNotNone(response.data['userprofile']) def test_m2m_user_link_remove_existing_link(self): ext_user = get_user_model().objects.create(username=str(uuid.uuid4()), urlid='http://external.user/user/1') diff --git a/djangoldp/urls.py b/djangoldp/urls.py index 49db770cbe66344c89f35c0102653fb6342b35a0..424f1afbbf55c0ffe0ffe7fc0e661ca31bcee70a 100644 --- a/djangoldp/urls.py +++ b/djangoldp/urls.py @@ -1,7 +1,7 @@ from importlib import import_module from django.conf import settings -from django.conf.urls import url, include +from django.conf.urls import re_path, include from djangoldp.models import LDPSource, Model from djangoldp.permissions import LDPPermissions @@ -19,21 +19,21 @@ def __clean_path(path): urlpatterns = [ - url(r'^sources/(?P<federation>\w+)/', LDPSourceViewSet.urls(model=LDPSource, fields=['federation', 'urlid'], + re_path(r'^sources/(?P<federation>\w+)/', LDPSourceViewSet.urls(model=LDPSource, fields=['federation', 'urlid'], permission_classes=[LDPPermissions], )), - url(r'^\.well-known/webfinger/?$', WebFingerView.as_view()), - url(r'^inbox/$', InboxView.as_view()), + re_path(r'^\.well-known/webfinger/?$', WebFingerView.as_view()), + re_path(r'^inbox/$', InboxView.as_view()), ] for package in settings.DJANGOLDP_PACKAGES: try: import_module('{}.models'.format(package)) - urlpatterns.append(url(r'^', include('{}.djangoldp_urls'.format(package)))) + urlpatterns.append(re_path(r'^', include('{}.djangoldp_urls'.format(package)))) except ModuleNotFoundError: pass if 'djangoldp_account' not in settings.DJANGOLDP_PACKAGES: - urlpatterns.append(url(r'^users/', LDPViewSet.urls(model=settings.AUTH_USER_MODEL, permission_classes=[]))) + urlpatterns.append(re_path(r'^users/', LDPViewSet.urls(model=settings.AUTH_USER_MODEL, permission_classes=[]))) # fetch a list of all models which subclass DjangoLDP Model model_classes = {cls.__name__: cls for cls in Model.__subclasses__()} @@ -43,11 +43,11 @@ for class_name in model_classes: model_class = model_classes[class_name] # the path is the url for this model path = __clean_path(model_class.get_container_path()) - # urls_fct will be a method which generates urls for a ViewSet (defined in LDPViewSet) + # urls_fct will be a method which generates urls for a ViewSet (defined in LDPViewSetGenerator) urls_fct = model_class.get_view_set().urls - urlpatterns.append(url(r'^' + path, include( + urlpatterns.append(re_path(r'^' + path, urls_fct(model=model_class, lookup_field=Model.get_meta(model_class, 'lookup_field', 'pk'), permission_classes=Model.get_meta(model_class, 'permission_classes', [LDPPermissions]), fields=Model.get_meta(model_class, 'serializer_fields', []), - nested_fields=model_class.nested.fields())))) + nested_fields=model_class.nested.fields()))) diff --git a/djangoldp/views.py b/djangoldp/views.py index d08943348bd13ef0945b26563b3fbda86eb4eb21..6e12304e21c2d655e5b9593b9b577302ee4eafe2 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -1,11 +1,10 @@ -import copy import json from django.apps import apps from django.conf import settings -from django.conf.urls import url, include +from django.conf.urls import re_path, include from django.contrib.auth import get_user_model from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist -from django.core.urlresolvers import get_resolver +from django.urls import get_resolver from django.db import IntegrityError, transaction from django.http import JsonResponse, Http404 from django.shortcuts import get_object_or_404 @@ -14,7 +13,6 @@ from django.views import View from pyld import jsonld from rest_framework import status from rest_framework.authentication import SessionAuthentication -from rest_framework.exceptions import ValidationError from rest_framework.permissions import AllowAny from rest_framework.parsers import JSONParser from rest_framework.renderers import JSONRenderer @@ -328,9 +326,9 @@ class LDPViewSetGenerator(ModelViewSet): detail_expr = cls.get_detail_expr(**kwargs) urls = [ - url('^$', cls.as_view(cls.list_actions, **kwargs), name='{}-list'.format(model_name)), - url('^' + detail_expr + '$', cls.as_view(cls.detail_actions, **kwargs), - name='{}-detail'.format(model_name)), + re_path('^$', cls.as_view(cls.list_actions, **kwargs), name='{}-list'.format(model_name)), + re_path('^' + detail_expr + '$', cls.as_view(cls.detail_actions, **kwargs), + name='{}-detail'.format(model_name)), ] # append nested fields to the urls list @@ -346,7 +344,7 @@ class LDPViewSetGenerator(ModelViewSet): urls_fct = kwargs['view_set'].nested_urls # our custom view_set may override nested_urls else: urls_fct = cls.nested_urls - urls.append(url('^' + detail_expr + field + '/', urls_fct(field, **kwargs))) + urls.append(re_path('^' + detail_expr + field + '/', urls_fct(field, **kwargs))) return include(urls) @@ -496,11 +494,11 @@ class LDPViewSet(LDPViewSetGenerator): response["Access-Control-Allow-Credentials"] = 'true' response["Accept-Post"] = "application/ld+json" if response.status_code in [201, 200] and '@id' in response.data: - response["Location"] = response.data['@id'] + response["Location"] = str(response.data['@id']) else: pass response["Accept-Post"] = "application/ld+json" - if request.user.is_authenticated(): + if request.user.is_authenticated: try: response['User'] = request.user.webid() except AttributeError: diff --git a/setup.cfg b/setup.cfg index a07d9b5852954d89c8983e8e0bc50730d1d8ce6b..453495b3d13692a7c70a2e20f49fea36e8767071 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,14 +10,14 @@ license = MIT [options] packages = find: setup_requires = - django~=1.11 + django~=2.2 install_requires = - django~=1.11 + django~=2.2 django_rest_framework requests validators~=0.12 pyld==1.0.5 - django-guardian==2.0.0 + django-guardian==2.3.0 [options.extras_require] dev =