diff --git a/zds/tutorialv2/api/permissions.py b/zds/tutorialv2/api/permissions.py index a2c1d10833..6cd51caf68 100644 --- a/zds/tutorialv2/api/permissions.py +++ b/zds/tutorialv2/api/permissions.py @@ -2,10 +2,31 @@ class IsOwner(BasePermission): - def has_permission(self, request, view): - request_param_user = request.kwargs.get('user', 0) + owner_mark = 'author' + + @staticmethod + def is_owner(request): + + request_param_user = request.parser_context['kwargs'].get('user', '0') current_user = request.user - return current_user and current_user.pk == request_param_user + try: + return current_user and current_user.pk == int(request_param_user) + except ValueError: # not an int + return False + + def is_object_owner(self, request, object): + request_param_user = request.parser_context['kwargs'].get('user', 0) + try: + object_owner = getattr(object, self.owner_mark, None).pk + return request_param_user == object_owner + except AttributeError: + return False + + def has_permission(self, request, view): + return IsOwner.is_owner(request) + + def has_object_permission(self, request, view, obj): + return self.is_object_owner(request, obj) class CanModerate(DjangoModelPermissions): @@ -18,3 +39,8 @@ class CanModerate(DjangoModelPermissions): 'PATCH': ['%(app_label)s.change_%(model_name)s'], 'DELETE': ['%(app_label)s.delete_%(model_name)s'], } + + +class CanModerateOrIsOwner(CanModerate, IsOwner): + def has_permission(self, request, view): + return IsOwner.is_owner(request) or CanModerate.has_permission(self, request, view) diff --git a/zds/tutorialv2/api/serializers.py b/zds/tutorialv2/api/serializers.py index 8820a4effa..c4b0b62be5 100644 --- a/zds/tutorialv2/api/serializers.py +++ b/zds/tutorialv2/api/serializers.py @@ -1,4 +1,5 @@ import copy +import datetime import logging from collections import Counter @@ -7,13 +8,13 @@ from rest_framework.exceptions import ValidationError from rest_framework.fields import CharField, empty -from zds.api.serializers import ZdSModelSerializer from zds.tutorialv2.api.view_models import ChildrenViewModel, ChildrenListViewModel, UpdateChildrenListViewModel -from gettext import gettext as _ +from django.utils.translation import ugettext as _ from zds.tutorialv2.models.database import PublishableContent from zds.tutorialv2.utils import init_new_repo from zds.utils.forms import TagValidator +from zds.utils.models import SubCategory logger = logging.getLogger(__name__) @@ -26,6 +27,14 @@ def __init__(self, *, filter_function=None, **kwargs): super().__init__(**kwargs) self.filter_method = filter_function + def __deepcopy__(self, memodict): + args = [] + kwargs = { + key: (copy.deepcopy(value) if (key not in ('validators', 'regex', 'filter_function')) else value) + for key, value in self._kwargs.items() + } + return self.__class__(*args, **kwargs) + def to_internal_value(self, data): if isinstance(data, (list, tuple)): return super().to_internal_value(','.join(str(value) for value in data)) @@ -54,7 +63,7 @@ def decorated(*args, **kwargs): try: return func(*args, **kwargs) except exception1: - logger.warning('Error translated fril %s to %s', exception1, exception2(message)) + logger.warning('Error translated from %s to %s', exception1, exception2(message)) raise exception2(message) return decorated return wrapper @@ -73,9 +82,6 @@ class ChildrenListSerializer(serializers.Serializer): Serialize children list so that api can handle them """ - def update(self, instance, validated_data): - pass - extracts = serializers.ListField(child=ChildrenSerializer(), source='extracts') containers = serializers.ListField(child=ChildrenSerializer(), source='containers') extract_number = serializers.IntegerField(source='extracts.__len__') @@ -118,16 +124,21 @@ def is_valid(self, raise_exception=False): self._validated_data[field_name] = value if self._validated_data.get('extracts', None): self._validated_data['extracts'] = [ChildrenViewModel(**v) for v in self._validated_data['extracts']] + has_error = self.validate_extracts_structure(has_error, messages) if self.initial_data.get('containers', None): self._validated_data['containers'] = [ChildrenViewModel(**v) for v in self._validated_data['containers']] - if not all(c.child_type.lower() == 'extract' for c in self._validated_data.get('extracts', [])): - has_error = True - messages['extracts'] = _('un extrait est mal configuré') - if len(self._validated_data['extracts']) != len(set(e.title for e in self._validated_data['extracts'])): + has_error = self.validate_container_structure(has_error, messages) + self._validated_data['conclusion'] = self.initial_data.get('conclusion', '') + if not self._validated_data['extracts'] and not self._validated_data['containers']: has_error = True - titles = Counter(list(e.title for e in self._validated_data['extracts'])) - doubly = [key for key, v in titles.items() if v > 1] - messages['extracts'] = _('Certains titres sont en double : {}').format(','.join(doubly)) + messages['extracts'] = _('Le contenu semble vide.') + if raise_exception and has_error: + self._errors.update(messages) + raise ValidationError(self.errors) + + return not has_error + + def validate_container_structure(self, has_error, messages): if len(self._validated_data['containers']) != len(set(e.title for e in self._validated_data['containers'])): has_error = True titles = Counter(list(e.title for e in self._validated_data['containers'])) @@ -137,15 +148,18 @@ def is_valid(self, raise_exception=False): has_error = True messages['containers'] = _('Un conteneur est mal configuré') self._validated_data['introduction'] = self.initial_data.get('introduction', '') - self._validated_data['conclusion'] = self.initial_data.get('conclusion', '') - if not self._validated_data['extracts'] and not self._validated_data['containers']: - has_error = True - messages['extracts'] = _('Le contenu semble vide.') - if raise_exception and has_error: - self._errors.update(messages) - raise ValidationError(self.errors) + return has_error - return not has_error + def validate_extracts_structure(self, has_error, messages): + if not all(c.child_type.lower() == 'extract' for c in self._validated_data.get('extracts', [])): + has_error = True + messages['extracts'] = _('un extrait est mal configuré') + if len(self._validated_data['extracts']) != len(set(e.title for e in self._validated_data['extracts'])): + has_error = True + titles = Counter(list(e.title for e in self._validated_data['extracts'])) + doubly = [key for key, v in titles.items() if v > 1] + messages['extracts'] = _('Certains titres sont en double : {}').format(','.join(doubly)) + return has_error def to_representation(self, instance): dic_repr = {} @@ -176,7 +190,7 @@ class Meta: 'introduction', 'conclusion', 'original_sha') def is_valid(self, raise_exception=False): - error = not super(ChildrenListModifySerializer, self).is_valid(raise_exception) + error = not super().is_valid(raise_exception) messages = {} if not self._validated_data['original_sha']: messages['original_sha'] = _("Vous n'avez pas fourni de marqueur de version") @@ -193,27 +207,32 @@ def create(self, validated_data): return UpdateChildrenListViewModel(**validated_data) -class PublishableMetaDataSerializer(ZdSModelSerializer): - tags = CommaSeparatedCharField(source='tags', required=False, filter_function=TagValidator().validate_one_element) +class PublishableMetaDataSerializer(serializers.ModelSerializer): + tags = CommaSeparatedCharField(required=False, filter_function=TagValidator().validate_one_element) class Meta: model = PublishableContent - exclude = ('is_obsolete', 'must_reindex', 'last_note', 'helps', 'beta_topic', 'image', 'content_type_attribute') - read_only_fields = ('authors', 'gallery', 'public_version', 'js_support', 'is_locked', 'relative_images_path', + exclude = ('is_obsolete', 'must_reindex', 'last_note', 'helps', 'beta_topic', 'image') + read_only_fields = ('authors', 'gallery', 'public_version', 'is_locked', 'relative_images_path', 'sha_picked', 'sha_draft', 'sha_validation', 'sha_beta', 'sha_public', 'picked_date', 'update_date', 'pubdate', 'creation_date', 'slug') depth = 2 def create(self, validated_data): # default db values - validated_data['is_js'] = False # Always false when we create + validated_data['js_support'] = False # Always false when we create + validated_data['creation_date'] = datetime.datetime.now() # links to other entities tags = validated_data.pop('tags', '') content = super().create(validated_data) + content.save() content.add_tags(tags) - content.add_author(self.context['author']) init_new_repo(content, '', '', _('Création de {}').format(content.title), do_commit=True) + content.authors.add(self.context['author']) + content.create_gallery() + content.save() + content.ensure_author_gallery() return content def update(self, instance, validated_data): @@ -240,3 +259,9 @@ def update(self, instance, validated_data): do_commit=True ) return super.update(instance, working_dictionary) + + +class ContentCategorySerializer(serializers.ModelSerializer): + class Meta: + model = SubCategory + depth = 1 diff --git a/zds/tutorialv2/api/tests.py b/zds/tutorialv2/api/tests.py index 42ac0d6916..a1431b5ffa 100644 --- a/zds/tutorialv2/api/tests.py +++ b/zds/tutorialv2/api/tests.py @@ -1,7 +1,6 @@ import datetime from copy import deepcopy import os -import shutil from django.conf import settings from django.core.cache import caches @@ -19,8 +18,10 @@ from zds.member.factories import ProfileFactory from zds.tutorialv2.api.serializers import ChildrenListModifySerializer from zds.tutorialv2.api.view_models import UpdateChildrenListViewModel -from zds.tutorialv2.factories import ContentReactionFactory, PublishedContentFactory, PublishableContentFactory +from zds.tutorialv2.factories import ContentReactionFactory, PublishedContentFactory, PublishableContentFactory, \ + SubCategoryFactory, LicenceFactory from zds.tutorialv2.models.database import PublishableContent +from zds.tutorialv2.tests import TutorialTestMixin from zds.utils.models import CommentVote overridden_zds_app = deepcopy(settings.ZDS_APP) @@ -32,8 +33,9 @@ @override_settings(MEDIA_ROOT=os.path.join(settings.BASE_DIR, 'media-test')) @override_settings(ZDS_APP=overridden_zds_app) -class ContentReactionKarmaAPITest(APITestCase): +class ContentReactionKarmaAPITest(TestCase, TutorialTestMixin): def setUp(self): + self.overridden_zds_app = overridden_zds_app self.client = APIClient() caches[extensions_api_settings.DEFAULT_USE_CACHE].clear() self.content = PublishedContentFactory() @@ -47,14 +49,6 @@ def test_failure_reaction_karma_with_client_unauthenticated(self): response = self.client.put(reverse('api:content:reaction-karma', args=(reaction.pk,))) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - def tearDown(self): - if os.path.isdir(settings.ZDS_APP['content']['repo_private_path']): - shutil.rmtree(settings.ZDS_APP['content']['repo_private_path']) - if os.path.isdir(settings.ZDS_APP['content']['repo_public_path']): - shutil.rmtree(settings.ZDS_APP['content']['repo_public_path']) - if os.path.isdir(settings.MEDIA_ROOT): - shutil.rmtree(settings.MEDIA_ROOT) - def test_failure_reaction_karma_with_sanctioned_user(self): author = ProfileFactory() reaction = ContentReactionFactory(author=author.user, position=1, related_content=self.content) @@ -205,16 +199,18 @@ def test_get_content_reaction_voters(self): class TestChildrenListSerializer(TestCase): + extracts = [ + {'title': 'Sit minus molestias omnis dolorum et tempora.', + 'text': "Ceci est un texte contenant plein d'images, pour la publication.", + 'child_type': 'extract', 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora'}, + {'title': 'un titre au milieu', 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', + 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} + ] + def test_good_input(self): data = { 'containers': [], - 'extracts': [ - {'title': 'Sit minus molestias omnis dolorum et tempora.', - 'text': "Ceci est un texte contenant plein d'images, pour la publication.", - 'child_type': 'extract', 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora'}, - {'title': 'un titre au milieu', 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', - 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} - ], + 'extracts': self.extracts, 'introduction': 'Ceci est un texte bidon, **avec markown**', 'conclusion': 'Ceci est un texte bidon, **avec markown**', 'remove_deleted_children': True, 'message': 'edition', @@ -228,13 +224,8 @@ def test_good_input(self): def test_missing_not_optional_parameters(self): data = { 'containers': [], - 'extracts': [ - {'title': 'Sit minus molestias omnis dolorum et tempora.', - 'text': "Ceci est un texte contenant plein d'images, pour la publication.", - 'child_type': 'extract', 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora'}, - {'title': 'un titre au milieu', 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', - 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} - ], 'original_sha': 'd33fef58bedd39dc1c2d38f16305b10010e9058e'} + 'extracts': self.extracts, + 'original_sha': 'd33fef58bedd39dc1c2d38f16305b10010e9058e'} serializer = ChildrenListModifySerializer(data=data, db_object=PublishableContent(sha_draft='d33fef58bedd39dc1c2' 'd38f16305b10010e9058e')) @@ -247,31 +238,23 @@ def test_missing_not_optional_parameters(self): self.assertFalse(created.remove_deleted_children) def test_bad_extract_type(self): + bad_extract = deepcopy(self.extracts[0]) + bad_extract['child_type'] = 'extracts' data = { 'containers': [], - 'extracts': [ - {'title': 'Sit minus molestias omnis dolorum et tempora.', - 'text': "Ceci est un texte contenant plein d'images, pour la publication.", - 'child_type': 'extracts', 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora'}, - {'title': 'un titre au milieu', 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', - 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} - ], 'original_sha': 'd33fef58bedd39dc1c2d38f16305b10010e9058e'} + 'extracts': [bad_extract, self.extracts[1]], + 'original_sha': 'd33fef58bedd39dc1c2d38f16305b10010e9058e'} serializer = ChildrenListModifySerializer(data=data, db_object=PublishableContent(sha_draft='d33fef58bedd39dc1c2' 'd38f16305b10010e9058e')) self.assertRaises(ValidationError, serializer.is_valid, raise_exception=True) def test_bad_extract_format(self): + bad_extract = deepcopy(self.extracts[0]) + bad_extract['description'] = 'extracts has no description' data = { 'containers': [], - 'extracts': [ - {'title': 'Sit minus molestias omnis dolorum et tempora.', - 'text': "Ceci est un texte contenant plein d'images, pour la publication.", - 'child_type': 'extract', 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora', - 'description': 'this is a description, extracts has no description'}, - {'title': 'un titre au milieu', 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', - 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} - ], + 'extracts': [bad_extract, self.extracts[1]], 'introduction': 'Ceci est un texte bidon, **avec markown**', 'conclusion': 'Ceci est un texte bidon, **avec markown**', 'remove_deleted_children': True, 'message': 'edition', @@ -282,8 +265,18 @@ def test_bad_extract_format(self): self.assertRaises(ValidationError, serializer.is_valid, raise_exception=True) -class ContentAPIChildrenUpdate(APITestCase): +class ContentAPIChildrenUpdate(APITestCase, TutorialTestMixin): + extracts = [ + {'title': 'Sit minus molestias omnis dolorum et tempora.', + 'text': "Ceci est un texte contenant plein d'images, pour la publication.", + 'child_type': 'extract', 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora'}, + {'title': 'un titre au milieu', 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', + 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} + ] + def setUp(self): + self.overridden_zds_app = overridden_zds_app + self.overridden_zds_app['content']['build_pdf_when_published'] = False self.author = ProfileFactory().user self.client_user = ProfileFactory() @@ -299,15 +292,7 @@ def test_add_extract(self): data={ 'original_sha': self.content.sha_draft, 'containers': [], - 'extracts': [ - {'title': 'Sit minus molestias omnis dolorum et tempora.', - 'text': "Ceci est un texte contenant plein d'images, pour la publication.", - 'child_type': 'extract', - 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora'}, - {'title': 'un titre au milieu', - 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', - 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} - ], + 'extracts': self.extracts, 'introduction': 'Ceci est un texte bidon, **avec markown**', 'conclusion': 'Ceci est un texte bidon, **avec markown**', 'remove_deleted_children': True, 'message': 'edition'}) @@ -320,17 +305,7 @@ def test_forbidden_access(self): data={ 'original_sha': self.content.sha_draft, 'containers': [], - 'extracts': [{ - 'title': 'Sit minus molestias omnis dolorum et tempora.', - 'text': "Ceci est un texte contenant plein d'images, pour la publication.", - 'child_type': 'extract', - 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora' - }, { - 'title': 'un titre au milieu', - 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', - 'child_type': 'extract', 'slug': 'un-titre-au-milieu' - } - ], + 'extracts': self.extracts, 'introduction': 'Ceci est un texte bidon, **avec markown**', 'conclusion': 'Ceci est un texte bidon, **avec markown**', 'remove_deleted_children': True, 'message': 'edition'} @@ -343,15 +318,7 @@ def test_insert_new_extract(self): data={ 'original_sha': self.content.sha_draft, 'containers': [], - 'extracts': [ - {'title': 'Sit minus molestias omnis dolorum et tempora.', - 'text': "Ceci est un texte contenant plein d'images, pour la publication.", - 'child_type': 'extract', - 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora'}, - {'title': 'un titre au milieu', - 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', - 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} - ], + 'extracts': self.extracts, 'introduction': 'Ceci est un texte bidon, **avec markown**', 'conclusion': 'Ceci est un texte bidon, **avec markown**', 'remove_deleted_children': True, 'message': 'edition'}) @@ -363,32 +330,51 @@ def test_insert_new_extract(self): 'original_sha': saved.sha_draft, 'containers': [], 'extracts': [ - {'title': 'Sit minus molestias omnis dolorum et tempora.', - 'text': "Ceci est un texte contenant plein d'images, pour la publication.", - 'child_type': 'extract', - 'slug': 'sit-minus-molestias-omnis-dolorum-et-tempora'}, + self.extracts[0], {'title': 'un titre au inséré', 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', 'child_type': 'extract', 'slug': 'un-titre-insere'}, - {'title': 'un titre au milieu', - 'text': 'blabla bla\n\n# Et donc ...\n\nVoilà :)', - 'child_type': 'extract', 'slug': 'un-titre-au-milieu'} + self.extracts[1] ], 'introduction': 'Ceci est un texte bidon, **avec markown**', 'conclusion': 'Ceci est un texte bidon, **avec markown**', 'remove_deleted_children': True, 'message': 'edition'}) + self.assertEqual(status.HTTP_200_OK, resp.status_code) new_version = PublishableContent.objects.get(pk=self.content.pk) self.assertNotEqual(new_version.sha_draft, saved.sha_draft) self.assertEqual(3, len(new_version.load_version().children)) self.assertEqual('un-titre-insere', new_version.load_version().children[1].slug) - def tearDown(self): - if os.path.isdir(settings.ZDS_APP['content']['repo_private_path']): - shutil.rmtree(settings.ZDS_APP['content']['repo_private_path']) - if os.path.isdir(settings.ZDS_APP['content']['repo_public_path']): - shutil.rmtree(settings.ZDS_APP['content']['repo_public_path']) - if os.path.isdir(settings.MEDIA_ROOT): - shutil.rmtree(settings.MEDIA_ROOT) - # re-active PDF build - settings.ZDS_APP['content']['build_pdf_when_published'] = True +@override_settings(MEDIA_ROOT=os.path.join(settings.BASE_DIR, 'media-test')) +@override_settings(ZDS_APP=overridden_zds_app) +class CreateContentWithAPITest(APITestCase, TutorialTestMixin): + def setUp(self): + self.overridden_zds_app = overridden_zds_app + caches[extensions_api_settings.DEFAULT_USE_CACHE].clear() + self.author = ProfileFactory().user + self.client_user = ProfileFactory() + + self.client = APIClient() + caches[extensions_api_settings.DEFAULT_USE_CACHE].clear() + self.client_oauth2 = create_oauth2_client(self.client_user.user) + + def test_create_content(self): + authenticate_client(self.client, self.client_oauth2, self.author.username, 'hostel77') + self.assertTrue(self.client.login(username=self.author.username, password='hostel77')) + category = SubCategoryFactory() + licence = LicenceFactory() + resp = self.client.post(reverse('api:content:api-author-contents', kwargs={'user': self.author.pk}), data={ + 'title': 'content-api', + 'description': 'my description', + 'type': 'ARTICLE', + 'subcategory': category.pk, + 'tags': 'tag1,tag2', + 'licence': licence.pk, + }) + self.assertEqual(status.HTTP_201_CREATED, resp.status_code) + last_content = PublishableContent.objects.last() + self.assertIsNotNone(last_content) + content = PublishableContent.objects.get(authors__in=[self.author]) + self.assertEqual('content-api', content.title) + self.assertEqual(2, content.tags.count()) diff --git a/zds/tutorialv2/api/urls.py b/zds/tutorialv2/api/urls.py index 11832d8567..49074bec2f 100644 --- a/zds/tutorialv2/api/urls.py +++ b/zds/tutorialv2/api/urls.py @@ -1,7 +1,11 @@ from django.conf.urls import url +from rest_framework.generics import ListAPIView -from zds.tutorialv2.api.views import ContentReactionKarmaView, ContainerPublicationReadinessView,\ - RedactionChildrenListView, AuthorContentListCreateAPIView +from zds.member.api.permissions import CanReadAndWriteNowOrReadOnly +from zds.tutorialv2.api.serializers import ContentCategorySerializer +from zds.tutorialv2.api.views import ContentReactionKarmaView, RedactionChildrenListView, \ + AuthorContentListCreateAPIView, InRedactionContentRetrieveUpdateDeleteAPIView, ContainerPublicationReadinessView +from zds.utils.models import SubCategory urlpatterns = [ url(r'^reactions/(?P\d+)/karma/?$', ContentReactionKarmaView.as_view(), name='reaction-karma'), @@ -21,7 +25,10 @@ AuthorContentListCreateAPIView.as_view(), name='api-author-contents'), url(r'^(?P\d+)/(?P[a-zA-Z0-9_-]+)/$', - AuthorContentListCreateAPIView.as_view(), + InRedactionContentRetrieveUpdateDeleteAPIView.as_view(), name='api-author-contents'), + url('categories/', ListAPIView.as_view(permission_classes=(CanReadAndWriteNowOrReadOnly,), + serializer_class=ContentCategorySerializer, + queryset=SubCategory.objects.all())) ] diff --git a/zds/tutorialv2/api/views.py b/zds/tutorialv2/api/views.py index 7a26145ec8..4be0fd2045 100644 --- a/zds/tutorialv2/api/views.py +++ b/zds/tutorialv2/api/views.py @@ -9,8 +9,9 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly from zds.member.api.permissions import IsAuthorOrStaff from rest_framework.response import Response +from zds.member.models import User from zds.member.api.permissions import CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly -from zds.tutorialv2.api.permissions import IsOwner, CanModerate +from zds.tutorialv2.api.permissions import CanModerateOrIsOwner from zds.tutorialv2.api.serializers import ChildrenListSerializer, ChildrenListModifySerializer, \ PublishableMetaDataSerializer from zds.tutorialv2.api.view_models import ChildrenListViewModel, ChildrenViewModel @@ -149,15 +150,19 @@ def get_permissions(self): class AuthorContentListCreateAPIView(ListCreateAPIView): - permission_classes = (IsOwner, CanModerate) + permission_classes = (CanModerateOrIsOwner,) serializer_class = PublishableMetaDataSerializer def get_queryset(self): return PublishableContent.objects.filter(authors__pk__in=[self.kwargs.get('user')]) + def perform_create(self, serializer): + serializer.context['author'] = get_object_or_404(User, pk=self.kwargs.get('user')) + super().perform_create(serializer) + class InRedactionContentRetrieveUpdateDeleteAPIView(RetrieveUpdateDestroyAPIView): - permission_classes = (IsOwner, CanModerate) + permission_classes = (CanModerateOrIsOwner,) serializer_class = PublishableMetaDataSerializer def get_object(self): diff --git a/zds/tutorialv2/models/database.py b/zds/tutorialv2/models/database.py index 3623c86457..3bff2a3602 100644 --- a/zds/tutorialv2/models/database.py +++ b/zds/tutorialv2/models/database.py @@ -32,7 +32,7 @@ from zds.tutorialv2.models.versioned import NotAPublicVersion from zds.tutorialv2.models import TYPE_CHOICES, STATUS_CHOICES, CONTENT_TYPES_REQUIRING_VALIDATION, PICK_OPERATIONS from zds.tutorialv2.utils import get_content_from_json, BadManifestError -from zds.utils import get_current_user +from zds.utils import get_current_user, slugify from zds.utils.models import SubCategory, Licence, HelpWriting, Comment, Tag from zds.searchv2.models import AbstractESDjangoIndexable, AbstractESIndexable, delete_document_in_elasticsearch, \ ESIndexManager @@ -144,6 +144,26 @@ class Meta: def __str__(self): return self.title + def create_gallery(self): + """ + Generates a new gallery for contents. + """ + gal = Gallery.objects.create( + title=self.title, + slug=self.slug or slugify(self.title), + pubdate=datetime.now()) + self.gallery = gal + + def set_icon(self, icon_file): + img = Image() + img.physical = icon_file + img.gallery = self.gallery + img.title = icon_file + img.slug = slugify(icon_file.name) + img.pubdate = datetime.now() + img.save() + self.image = img + def update(self, **fields): """ wrapper arround ``self.objects.update`` diff --git a/zds/tutorialv2/utils.py b/zds/tutorialv2/utils.py index 95a17a06f3..2c430b7e08 100644 --- a/zds/tutorialv2/utils.py +++ b/zds/tutorialv2/utils.py @@ -595,7 +595,7 @@ def init_new_repo(db_object, introduction_text, conclusion_text, commit_message= os.makedirs(path, mode=0o777) # init repo: - Repo.init(path, bare=False, template='') + repo = Repo.init(path, bare=False, template='') # create object versioned_content = VersionedContent(None, db_object.type, db_object.title, db_object.slug) @@ -603,7 +603,7 @@ def init_new_repo(db_object, introduction_text, conclusion_text, commit_message= # fill some information that are missing : versioned_content.licence = db_object.licence versioned_content.description = db_object.description - + versioned_content.repository = repo # perform changes: if not commit_message: commit_message = 'Création du contenu' diff --git a/zds/tutorialv2/views/contents.py b/zds/tutorialv2/views/contents.py index 0f1ebb8c42..b4b322d61e 100644 --- a/zds/tutorialv2/views/contents.py +++ b/zds/tutorialv2/views/contents.py @@ -110,25 +110,9 @@ def form_valid(self, form): self.content.creation_date = datetime.now() # Creating the gallery - gal = Gallery() - gal.title = form.cleaned_data['title'] - gal.slug = slugify(form.cleaned_data['title']) - gal.pubdate = datetime.now() - gal.save() - - self.content.gallery = gal - self.content.save() - # create image: + self.content.create_gallery() if 'image' in self.request.FILES: - img = Image() - img.physical = self.request.FILES['image'] - img.gallery = gal - img.title = self.request.FILES['image'] - img.slug = slugify(self.request.FILES['image'].name) - img.pubdate = datetime.now() - img.save() - self.content.image = img - + self.content.set_icon(self.request.FILES['image']) self.content.save() # We need to save the content before changing its author list since it's a many-to-many relationship