diff --git a/CHANGELOG.md b/CHANGELOG.md index b0561170..5c3342a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ and this project adheres to - ✨(frontend) integrate configurable Waffle #1795 +### Changed + +- ♿(frontend) improve accessibility: + - ♿️(frontend) fix subdoc opening and emoji pick focus #1745 +- ✨(backend) add field for button label in email template #1817 + ### Fixed - ✅(e2e) fix e2e test for other browsers #1799 @@ -17,17 +23,14 @@ and this project adheres to - 🐛(frontend) fix emojipicker closing in tree #1808 - 🐛(frontend) display children in favorite #1782 -### Changed +### Removed -- ♿(frontend) improve accessibility: - - ♿️(frontend) fix subdoc opening and emoji pick focus #1745 -- ✨(backend) add field for button label in email template #1817 +- 🔥(project) remove all code related to template #1780 ### Security - 🔒️(trivy) fix vulnerability about jaraco.context #1806 - ## [4.4.0] - 2026-01-13 ### Added diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index 88329030..c6de8067 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -9,14 +9,6 @@ from treebeard.admin import TreeAdmin from . import models -class TemplateAccessInline(admin.TabularInline): - """Inline admin class for template accesses.""" - - autocomplete_fields = ["user"] - model = models.TemplateAccess - extra = 0 - - @admin.register(models.User) class UserAdmin(auth_admin.UserAdmin): """Admin class for the User model""" @@ -69,7 +61,6 @@ class UserAdmin(auth_admin.UserAdmin): }, ), ) - inlines = (TemplateAccessInline,) list_display = ( "id", "sub", @@ -104,15 +95,8 @@ class UserAdmin(auth_admin.UserAdmin): search_fields = ("id", "sub", "admin_email", "email", "full_name") -@admin.register(models.Template) -class TemplateAdmin(admin.ModelAdmin): - """Template admin interface declaration.""" - - inlines = (TemplateAccessInline,) - - class DocumentAccessInline(admin.TabularInline): - """Inline admin class for template accesses.""" + """Inline admin class for document accesses.""" autocomplete_fields = ["user"] model = models.DocumentAccess diff --git a/src/backend/core/api/permissions.py b/src/backend/core/api/permissions.py index 29df311c..affd9b07 100644 --- a/src/backend/core/api/permissions.py +++ b/src/backend/core/api/permissions.py @@ -98,10 +98,10 @@ class CanCreateInvitationPermission(permissions.BasePermission): class ResourceWithAccessPermission(permissions.BasePermission): - """A permission class for templates and invitations.""" + """A permission class for invitations.""" def has_permission(self, request, view): - """check create permission for templates.""" + """check create permission.""" return request.user.is_authenticated or view.action != "create" def has_object_permission(self, request, view, obj): diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py index 6c224513..82a7c55b 100644 --- a/src/backend/core/api/serializers.py +++ b/src/backend/core/api/serializers.py @@ -59,30 +59,6 @@ class UserLightSerializer(UserSerializer): read_only_fields = ["full_name", "short_name"] -class TemplateAccessSerializer(serializers.ModelSerializer): - """Serialize template accesses.""" - - abilities = serializers.SerializerMethodField(read_only=True) - - class Meta: - model = models.TemplateAccess - resource_field_name = "template" - fields = ["id", "user", "team", "role", "abilities"] - read_only_fields = ["id", "abilities"] - - def get_abilities(self, instance) -> dict: - """Return abilities of the logged-in user on the instance.""" - request = self.context.get("request") - if request: - return instance.get_abilities(request.user) - return {} - - def update(self, instance, validated_data): - """Make "user" field is readonly but only on update.""" - validated_data.pop("user", None) - return super().update(instance, validated_data) - - class ListDocumentSerializer(serializers.ModelSerializer): """Serialize documents with limited fields for display in lists.""" @@ -660,52 +636,6 @@ class FileUploadSerializer(serializers.Serializer): return attrs -class TemplateSerializer(serializers.ModelSerializer): - """Serialize templates.""" - - abilities = serializers.SerializerMethodField(read_only=True) - accesses = TemplateAccessSerializer(many=True, read_only=True) - - class Meta: - model = models.Template - fields = [ - "id", - "title", - "accesses", - "abilities", - "css", - "code", - "is_public", - ] - read_only_fields = ["id", "accesses", "abilities"] - - def get_abilities(self, document) -> dict: - """Return abilities of the logged-in user on the instance.""" - request = self.context.get("request") - if request: - return document.get_abilities(request.user) - return {} - - -# pylint: disable=abstract-method -class DocumentGenerationSerializer(serializers.Serializer): - """Serializer to receive a request to generate a document on a template.""" - - body = serializers.CharField(label=_("Body")) - body_type = serializers.ChoiceField( - choices=["html", "markdown"], - label=_("Body type"), - required=False, - default="html", - ) - format = serializers.ChoiceField( - choices=["pdf", "docx"], - label=_("Format"), - required=False, - default="pdf", - ) - - class InvitationSerializer(serializers.ModelSerializer): """Serialize invitations.""" diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 3b62b893..e80c8cd3 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -2109,64 +2109,6 @@ class DocumentAccessViewSet( ) -class TemplateViewSet( - drf.mixins.RetrieveModelMixin, - viewsets.GenericViewSet, -): - """Template ViewSet""" - - filter_backends = [drf.filters.OrderingFilter] - permission_classes = [ - permissions.IsAuthenticatedOrSafe, - permissions.ResourceWithAccessPermission, - ] - throttle_scope = "template" - ordering = ["-created_at"] - ordering_fields = ["created_at", "updated_at", "title"] - serializer_class = serializers.TemplateSerializer - queryset = models.Template.objects.all() - - def get_queryset(self): - """Custom queryset to get user related templates.""" - queryset = super().get_queryset() - user = self.request.user - - if not user.is_authenticated: - return queryset - - user_roles_query = ( - models.TemplateAccess.objects.filter( - db.Q(user=user) | db.Q(team__in=user.teams), - template_id=db.OuterRef("pk"), - ) - .values("template") - .annotate(roles_array=ArrayAgg("role")) - .values("roles_array") - ) - return queryset.annotate(user_roles=db.Subquery(user_roles_query)).distinct() - - def list(self, request, *args, **kwargs): - """Restrict templates returned by the list endpoint""" - queryset = self.filter_queryset(self.get_queryset()) - user = self.request.user - if user.is_authenticated: - queryset = queryset.filter( - db.Q(accesses__user=user) - | db.Q(accesses__team__in=user.teams) - | db.Q(is_public=True) - ) - else: - queryset = queryset.filter(is_public=True) - - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - - serializer = self.get_serializer(queryset, many=True) - return drf.response.Response(serializer.data) - - class InvitationViewset( drf.mixins.CreateModelMixin, drf.mixins.ListModelMixin, diff --git a/src/backend/core/factories.py b/src/backend/core/factories.py index c0737cdc..acc39e06 100644 --- a/src/backend/core/factories.py +++ b/src/backend/core/factories.py @@ -53,15 +53,6 @@ class UserFactory(factory.django.DjangoModelFactory): if create and (extracted is True): UserDocumentAccessFactory(user=self, role="owner") - @factory.post_generation - def with_owned_template(self, create, extracted, **kwargs): - """ - Create a template for which the user is owner to check - that there is no interference - """ - if create and (extracted is True): - UserTemplateAccessFactory(user=self, role="owner") - class ParentNodeFactory(factory.declarations.ParameteredAttribute): """Custom factory attribute for setting the parent node.""" @@ -202,50 +193,6 @@ class DocumentAskForAccessFactory(factory.django.DjangoModelFactory): role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices]) -class TemplateFactory(factory.django.DjangoModelFactory): - """A factory to create templates""" - - class Meta: - model = models.Template - django_get_or_create = ("title",) - skip_postgeneration_save = True - - title = factory.Sequence(lambda n: f"template{n}") - is_public = factory.Faker("boolean") - - @factory.post_generation - def users(self, create, extracted, **kwargs): - """Add users to template from a given list of users with or without roles.""" - if create and extracted: - for item in extracted: - if isinstance(item, models.User): - UserTemplateAccessFactory(template=self, user=item) - else: - UserTemplateAccessFactory(template=self, user=item[0], role=item[1]) - - -class UserTemplateAccessFactory(factory.django.DjangoModelFactory): - """Create fake template user accesses for testing.""" - - class Meta: - model = models.TemplateAccess - - template = factory.SubFactory(TemplateFactory) - user = factory.SubFactory(UserFactory) - role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices]) - - -class TeamTemplateAccessFactory(factory.django.DjangoModelFactory): - """Create fake template team accesses for testing.""" - - class Meta: - model = models.TemplateAccess - - template = factory.SubFactory(TemplateFactory) - team = factory.Sequence(lambda n: f"team{n}") - role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices]) - - class InvitationFactory(factory.django.DjangoModelFactory): """A factory to create invitations for a user""" diff --git a/src/backend/core/migrations/0028_remove_templateaccess_template_and_more.py b/src/backend/core/migrations/0028_remove_templateaccess_template_and_more.py new file mode 100644 index 00000000..5de5e370 --- /dev/null +++ b/src/backend/core/migrations/0028_remove_templateaccess_template_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 5.2.9 on 2026-01-09 14:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0027_auto_20251120_0956"), + ] + + operations = [ + migrations.RemoveField( + model_name="templateaccess", + name="template", + ), + migrations.RemoveField( + model_name="templateaccess", + name="user", + ), + migrations.DeleteModel( + name="Template", + ), + migrations.DeleteModel( + name="TemplateAccess", + ), + ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 9f4e3721..25e97bd1 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -1430,163 +1430,6 @@ class Reaction(BaseModel): return f"Reaction {self.emoji} on comment {self.comment.id}" -class Template(BaseModel): - """HTML and CSS code used for formatting the print around the MarkDown body.""" - - title = models.CharField(_("title"), max_length=255) - description = models.TextField(_("description"), blank=True) - code = models.TextField(_("code"), blank=True) - css = models.TextField(_("css"), blank=True) - is_public = models.BooleanField( - _("public"), - default=False, - help_text=_("Whether this template is public for anyone to use."), - ) - - class Meta: - db_table = "impress_template" - ordering = ("title",) - verbose_name = _("Template") - verbose_name_plural = _("Templates") - - def __str__(self): - return self.title - - def get_role(self, user): - """Return the roles a user has on a resource as an iterable.""" - if not user.is_authenticated: - return None - - try: - roles = self.user_roles or [] - except AttributeError: - try: - roles = self.accesses.filter( - models.Q(user=user) | models.Q(team__in=user.teams), - ).values_list("role", flat=True) - except (models.ObjectDoesNotExist, IndexError): - roles = [] - - return RoleChoices.max(*roles) - - def get_abilities(self, user): - """ - Compute and return abilities for a given user on the template. - """ - role = self.get_role(user) - is_owner_or_admin = role in PRIVILEGED_ROLES - can_get = self.is_public or bool(role) - can_update = is_owner_or_admin or role == RoleChoices.EDITOR - - return { - "destroy": role == RoleChoices.OWNER, - "generate_document": can_get, - "accesses_manage": is_owner_or_admin, - "update": can_update, - "partial_update": can_update, - "retrieve": can_get, - } - - -class TemplateAccess(BaseAccess): - """Relation model to give access to a template for a user or a team with a role.""" - - template = models.ForeignKey( - Template, - on_delete=models.CASCADE, - related_name="accesses", - ) - - class Meta: - db_table = "impress_template_access" - ordering = ("-created_at",) - verbose_name = _("Template/user relation") - verbose_name_plural = _("Template/user relations") - constraints = [ - models.UniqueConstraint( - fields=["user", "template"], - condition=models.Q(user__isnull=False), # Exclude null users - name="unique_template_user", - violation_error_message=_("This user is already in this template."), - ), - models.UniqueConstraint( - fields=["team", "template"], - condition=models.Q(team__gt=""), # Exclude empty string teams - name="unique_template_team", - violation_error_message=_("This team is already in this template."), - ), - models.CheckConstraint( - condition=models.Q(user__isnull=False, team="") - | models.Q(user__isnull=True, team__gt=""), - name="check_template_access_either_user_or_team", - violation_error_message=_("Either user or team must be set, not both."), - ), - ] - - def __str__(self): - return f"{self.user!s} is {self.role:s} in template {self.template!s}" - - def get_role(self, user): - """ - Get the role a user has on a resource. - """ - if not user.is_authenticated: - return None - - try: - roles = self.user_roles or [] - except AttributeError: - teams = user.teams - try: - roles = self.template.accesses.filter( - models.Q(user=user) | models.Q(team__in=teams), - ).values_list("role", flat=True) - except (Template.DoesNotExist, IndexError): - roles = [] - - return RoleChoices.max(*roles) - - def get_abilities(self, user): - """ - Compute and return abilities for a given user on the template access. - """ - role = self.get_role(user) - is_owner_or_admin = role in PRIVILEGED_ROLES - - if self.role == RoleChoices.OWNER: - can_delete = (role == RoleChoices.OWNER) and self.template.accesses.filter( - role=RoleChoices.OWNER - ).count() > 1 - set_role_to = ( - [RoleChoices.ADMIN, RoleChoices.EDITOR, RoleChoices.READER] - if can_delete - else [] - ) - else: - can_delete = is_owner_or_admin - set_role_to = [] - if role == RoleChoices.OWNER: - set_role_to.append(RoleChoices.OWNER) - if is_owner_or_admin: - set_role_to.extend( - [RoleChoices.ADMIN, RoleChoices.EDITOR, RoleChoices.READER] - ) - - # Remove the current role as we don't want to propose it as an option - try: - set_role_to.remove(self.role) - except ValueError: - pass - - return { - "destroy": can_delete, - "update": bool(set_role_to), - "partial_update": bool(set_role_to), - "retrieve": bool(role), - "set_role_to": set_role_to, - } - - class Invitation(BaseModel): """User invitation to a document.""" diff --git a/src/backend/core/templates/core/generate_document.html b/src/backend/core/templates/core/generate_document.html deleted file mode 100644 index ac9f2915..00000000 --- a/src/backend/core/templates/core/generate_document.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - Generate Document - - -

Generate Document

-
- {% csrf_token %} - {{ form.as_p }} - -
- - diff --git a/src/backend/core/templatetags/extra_tags.py b/src/backend/core/templatetags/extra_tags.py index 109bd7b0..273459ff 100644 --- a/src/backend/core/templatetags/extra_tags.py +++ b/src/backend/core/templatetags/extra_tags.py @@ -1,4 +1,4 @@ -"""Custom template tags for the core application of People.""" +"""Custom template tags for the core application of Docs.""" import base64 diff --git a/src/backend/core/tests/templates/test_api_templates_create.py b/src/backend/core/tests/templates/test_api_templates_create.py deleted file mode 100644 index 3935d0ac..00000000 --- a/src/backend/core/tests/templates/test_api_templates_create.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Tests for Templates API endpoint in impress's core app: create -""" - -import pytest -from rest_framework.test import APIClient - -from core import factories -from core.models import Template - -pytestmark = pytest.mark.django_db - - -def test_api_templates_create_anonymous(): - """Anonymous users should not be allowed to create templates.""" - response = APIClient().post( - "/api/v1.0/templates/", - { - "title": "my template", - }, - ) - - assert response.status_code == 401 - assert not Template.objects.exists() - - -def test_api_templates_create_authenticated(): - """ - Authenticated users should be able to create templates and should automatically be declared - as the owner of the newly created template. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - response = client.post( - "/api/v1.0/templates/", - { - "title": "my template", - }, - format="json", - ) - - assert response.status_code == 405 - assert not Template.objects.exists() diff --git a/src/backend/core/tests/templates/test_api_templates_delete.py b/src/backend/core/tests/templates/test_api_templates_delete.py deleted file mode 100644 index 8db834de..00000000 --- a/src/backend/core/tests/templates/test_api_templates_delete.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Tests for Templates API endpoint in impress's core app: delete -""" - -import random - -import pytest -from rest_framework.test import APIClient - -from core import factories, models - -pytestmark = pytest.mark.django_db - - -def test_api_templates_delete_anonymous(): - """Anonymous users should not be allowed to destroy a template.""" - template = factories.TemplateFactory() - - response = APIClient().delete( - f"/api/v1.0/templates/{template.id!s}/", - ) - - assert response.status_code == 401 - assert models.Template.objects.count() == 1 - - -def test_api_templates_delete_not_implemented(): - """ - Authenticated users should not be allowed to delete a template to which they are not - related. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - is_public = random.choice([True, False]) - template = factories.TemplateFactory(is_public=is_public, users=[(user, "owner")]) - - response = client.delete( - f"/api/v1.0/templates/{template.id!s}/", - ) - - assert response.status_code == 405 - assert models.Template.objects.count() == 1 diff --git a/src/backend/core/tests/templates/test_api_templates_list.py b/src/backend/core/tests/templates/test_api_templates_list.py deleted file mode 100644 index f4980354..00000000 --- a/src/backend/core/tests/templates/test_api_templates_list.py +++ /dev/null @@ -1,237 +0,0 @@ -""" -Tests for Templates API endpoint in impress's core app: list -""" - -from unittest import mock - -import pytest -from rest_framework.pagination import PageNumberPagination -from rest_framework.test import APIClient - -from core import factories - -pytestmark = pytest.mark.django_db - - -def test_api_templates_list_anonymous(): - """Anonymous users should only be able to list public templates.""" - factories.TemplateFactory.create_batch(2, is_public=False) - public_templates = factories.TemplateFactory.create_batch(2, is_public=True) - expected_ids = {str(template.id) for template in public_templates} - - response = APIClient().get("/api/v1.0/templates/") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 2 - results_id = {result["id"] for result in results} - assert expected_ids == results_id - - -def test_api_templates_list_authenticated_direct(): - """ - Authenticated users should be able to list templates they are a direct - owner/administrator/member of or that are public. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - related_templates = [ - access.template - for access in factories.UserTemplateAccessFactory.create_batch(5, user=user) - ] - public_templates = factories.TemplateFactory.create_batch(2, is_public=True) - factories.TemplateFactory.create_batch(2, is_public=False) - - expected_ids = { - str(template.id) for template in related_templates + public_templates - } - - response = client.get( - "/api/v1.0/templates/", - ) - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 7 - results_id = {result["id"] for result in results} - assert expected_ids == results_id - - -def test_api_templates_list_authenticated_via_team(mock_user_teams): - """ - Authenticated users should be able to list templates they are a - owner/administrator/member of via a team or that are public. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - mock_user_teams.return_value = ["team1", "team2", "unknown"] - - templates_team1 = [ - access.template - for access in factories.TeamTemplateAccessFactory.create_batch(2, team="team1") - ] - templates_team2 = [ - access.template - for access in factories.TeamTemplateAccessFactory.create_batch(3, team="team2") - ] - public_templates = factories.TemplateFactory.create_batch(2, is_public=True) - factories.TemplateFactory.create_batch(2, is_public=False) - - expected_ids = { - str(template.id) - for template in templates_team1 + templates_team2 + public_templates - } - - response = client.get("/api/v1.0/templates/") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 7 - results_id = {result["id"] for result in results} - assert expected_ids == results_id - - -@mock.patch.object(PageNumberPagination, "get_page_size", return_value=2) -def test_api_templates_list_pagination( - _mock_page_size, -): - """Pagination should work as expected.""" - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template_ids = [ - str(access.template_id) - for access in factories.UserTemplateAccessFactory.create_batch(3, user=user) - ] - - # Get page 1 - response = client.get( - "/api/v1.0/templates/", - ) - - assert response.status_code == 200 - content = response.json() - - assert content["count"] == 3 - assert content["next"] == "http://testserver/api/v1.0/templates/?page=2" - assert content["previous"] is None - - assert len(content["results"]) == 2 - for item in content["results"]: - template_ids.remove(item["id"]) - - # Get page 2 - response = client.get( - "/api/v1.0/templates/?page=2", - ) - - assert response.status_code == 200 - content = response.json() - - assert content["count"] == 3 - assert content["next"] is None - assert content["previous"] == "http://testserver/api/v1.0/templates/" - - assert len(content["results"]) == 1 - template_ids.remove(content["results"][0]["id"]) - assert template_ids == [] - - -def test_api_templates_list_authenticated_distinct(): - """A template with several related users should only be listed once.""" - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - other_user = factories.UserFactory() - - template = factories.TemplateFactory(users=[user, other_user], is_public=True) - - response = client.get( - "/api/v1.0/templates/", - ) - - assert response.status_code == 200 - content = response.json() - assert len(content["results"]) == 1 - assert content["results"][0]["id"] == str(template.id) - - -def test_api_templates_list_order_default(): - """The templates list should be sorted by 'created_at' in descending order by default.""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - template_ids = [ - str(access.template.id) - for access in factories.UserTemplateAccessFactory.create_batch(5, user=user) - ] - - response = client.get( - "/api/v1.0/templates/", - ) - - assert response.status_code == 200 - - response_data = response.json() - response_template_ids = [template["id"] for template in response_data["results"]] - - template_ids.reverse() - assert response_template_ids == template_ids, ( - "created_at values are not sorted from newest to oldest" - ) - - -def test_api_templates_list_order_param(): - """ - The templates list is sorted by 'created_at' in ascending order when setting - the "ordering" query parameter. - """ - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - templates_ids = [ - str(access.template.id) - for access in factories.UserTemplateAccessFactory.create_batch(5, user=user) - ] - - response = client.get( - "/api/v1.0/templates/?ordering=created_at", - ) - assert response.status_code == 200 - - response_data = response.json() - - response_template_ids = [template["id"] for template in response_data["results"]] - - assert response_template_ids == templates_ids, ( - "created_at values are not sorted from oldest to newest" - ) - - -def test_api_template_throttling(settings): - """Test api template throttling.""" - current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template"] - settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template"] = "2/minute" - client = APIClient() - for _i in range(2): - response = client.get("/api/v1.0/templates/") - assert response.status_code == 200 - with mock.patch("core.api.throttling.capture_message") as mock_capture_message: - response = client.get("/api/v1.0/templates/") - assert response.status_code == 429 - mock_capture_message.assert_called_once_with( - "Rate limit exceeded for scope template", "warning" - ) - settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template"] = current_rate diff --git a/src/backend/core/tests/templates/test_api_templates_retrieve.py b/src/backend/core/tests/templates/test_api_templates_retrieve.py deleted file mode 100644 index e3466ab2..00000000 --- a/src/backend/core/tests/templates/test_api_templates_retrieve.py +++ /dev/null @@ -1,522 +0,0 @@ -""" -Tests for Templates API endpoint in impress's core app: retrieve -""" - -import pytest -from rest_framework.test import APIClient - -from core import factories - -pytestmark = pytest.mark.django_db - - -def test_api_templates_retrieve_anonymous_public(): - """Anonymous users should be allowed to retrieve public templates.""" - template = factories.TemplateFactory(is_public=True) - - response = APIClient().get(f"/api/v1.0/templates/{template.id!s}/") - - assert response.status_code == 200 - assert response.json() == { - "id": str(template.id), - "abilities": { - "destroy": False, - "generate_document": True, - "accesses_manage": False, - "partial_update": False, - "retrieve": True, - "update": False, - }, - "accesses": [], - "title": template.title, - "is_public": True, - "code": template.code, - "css": template.css, - } - - -def test_api_templates_retrieve_anonymous_not_public(): - """Anonymous users should not be able to retrieve a template that is not public.""" - template = factories.TemplateFactory(is_public=False) - - response = APIClient().get(f"/api/v1.0/templates/{template.id!s}/") - - assert response.status_code == 401 - assert response.json() == { - "detail": "Authentication credentials were not provided." - } - - -def test_api_templates_retrieve_authenticated_unrelated_public(): - """ - Authenticated users should be able to retrieve a public template to which they are - not related. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template = factories.TemplateFactory(is_public=True) - - response = client.get( - f"/api/v1.0/templates/{template.id!s}/", - ) - assert response.status_code == 200 - assert response.json() == { - "id": str(template.id), - "abilities": { - "destroy": False, - "generate_document": True, - "accesses_manage": False, - "partial_update": False, - "retrieve": True, - "update": False, - }, - "accesses": [], - "title": template.title, - "is_public": True, - "code": template.code, - "css": template.css, - } - - -def test_api_templates_retrieve_authenticated_unrelated_not_public(): - """ - Authenticated users should not be allowed to retrieve a template that is not public and - to which they are not related. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template = factories.TemplateFactory(is_public=False) - - response = client.get( - f"/api/v1.0/templates/{template.id!s}/", - ) - assert response.status_code == 403 - assert response.json() == { - "detail": "You do not have permission to perform this action." - } - - -def test_api_templates_retrieve_authenticated_related_direct(): - """ - Authenticated users should be allowed to retrieve a template to which they - are directly related whatever the role. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template = factories.TemplateFactory() - access1 = factories.UserTemplateAccessFactory(template=template, user=user) - access2 = factories.UserTemplateAccessFactory(template=template) - - response = client.get( - f"/api/v1.0/templates/{template.id!s}/", - ) - assert response.status_code == 200 - content = response.json() - assert sorted(content.pop("accesses"), key=lambda x: x["user"]) == sorted( - [ - { - "id": str(access1.id), - "user": str(user.id), - "team": "", - "role": access1.role, - "abilities": access1.get_abilities(user), - }, - { - "id": str(access2.id), - "user": str(access2.user.id), - "team": "", - "role": access2.role, - "abilities": access2.get_abilities(user), - }, - ], - key=lambda x: x["user"], - ) - assert response.json() == { - "id": str(template.id), - "title": template.title, - "abilities": template.get_abilities(user), - "is_public": template.is_public, - "code": template.code, - "css": template.css, - } - - -def test_api_templates_retrieve_authenticated_related_team_none(mock_user_teams): - """ - Authenticated users should not be able to retrieve a template related to teams in - which the user is not. - """ - mock_user_teams.return_value = [] - - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template = factories.TemplateFactory(is_public=False) - - factories.TeamTemplateAccessFactory( - template=template, team="readers", role="reader" - ) - factories.TeamTemplateAccessFactory( - template=template, team="editors", role="editor" - ) - factories.TeamTemplateAccessFactory( - template=template, team="administrators", role="administrator" - ) - factories.TeamTemplateAccessFactory(template=template, team="owners", role="owner") - factories.TeamTemplateAccessFactory(template=template) - factories.TeamTemplateAccessFactory() - - response = client.get(f"/api/v1.0/templates/{template.id!s}/") - assert response.status_code == 403 - assert response.json() == { - "detail": "You do not have permission to perform this action." - } - - -@pytest.mark.parametrize( - "teams", - [ - ["readers"], - ["unknown", "readers"], - ["editors"], - ["unknown", "editors"], - ], -) -def test_api_templates_retrieve_authenticated_related_team_readers_or_editors( - teams, mock_user_teams -): - """ - Authenticated users should be allowed to retrieve a template to which they - are related via a team whatever the role and see all its accesses. - """ - mock_user_teams.return_value = teams - - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template = factories.TemplateFactory(is_public=False) - - access_reader = factories.TeamTemplateAccessFactory( - template=template, team="readers", role="reader" - ) - access_editor = factories.TeamTemplateAccessFactory( - template=template, team="editors", role="editor" - ) - access_administrator = factories.TeamTemplateAccessFactory( - template=template, team="administrators", role="administrator" - ) - access_owner = factories.TeamTemplateAccessFactory( - template=template, team="owners", role="owner" - ) - other_access = factories.TeamTemplateAccessFactory(template=template) - factories.TeamTemplateAccessFactory() - - response = client.get(f"/api/v1.0/templates/{template.id!s}/") - assert response.status_code == 200 - content = response.json() - expected_abilities = { - "destroy": False, - "retrieve": True, - "set_role_to": [], - "update": False, - "partial_update": False, - } - assert sorted(content.pop("accesses"), key=lambda x: x["id"]) == sorted( - [ - { - "id": str(access_reader.id), - "user": None, - "team": "readers", - "role": access_reader.role, - "abilities": expected_abilities, - }, - { - "id": str(access_editor.id), - "user": None, - "team": "editors", - "role": access_editor.role, - "abilities": expected_abilities, - }, - { - "id": str(access_administrator.id), - "user": None, - "team": "administrators", - "role": access_administrator.role, - "abilities": expected_abilities, - }, - { - "id": str(access_owner.id), - "user": None, - "team": "owners", - "role": access_owner.role, - "abilities": expected_abilities, - }, - { - "id": str(other_access.id), - "user": None, - "team": other_access.team, - "role": other_access.role, - "abilities": expected_abilities, - }, - ], - key=lambda x: x["id"], - ) - assert response.json() == { - "id": str(template.id), - "title": template.title, - "abilities": template.get_abilities(user), - "is_public": False, - "code": template.code, - "css": template.css, - } - - -@pytest.mark.parametrize( - "teams", - [ - ["administrators"], - ["members", "administrators"], - ["unknown", "administrators"], - ], -) -def test_api_templates_retrieve_authenticated_related_team_administrators( - teams, mock_user_teams -): - """ - Authenticated users should be allowed to retrieve a template to which they - are related via a team whatever the role and see all its accesses. - """ - mock_user_teams.return_value = teams - - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template = factories.TemplateFactory(is_public=False) - - access_reader = factories.TeamTemplateAccessFactory( - template=template, team="readers", role="reader" - ) - access_editor = factories.TeamTemplateAccessFactory( - template=template, team="editors", role="editor" - ) - access_administrator = factories.TeamTemplateAccessFactory( - template=template, team="administrators", role="administrator" - ) - access_owner = factories.TeamTemplateAccessFactory( - template=template, team="owners", role="owner" - ) - other_access = factories.TeamTemplateAccessFactory(template=template) - factories.TeamTemplateAccessFactory() - - response = client.get(f"/api/v1.0/templates/{template.id!s}/") - - assert response.status_code == 200 - content = response.json() - assert sorted(content.pop("accesses"), key=lambda x: x["id"]) == sorted( - [ - { - "id": str(access_reader.id), - "user": None, - "team": "readers", - "role": "reader", - "abilities": { - "destroy": True, - "retrieve": True, - "set_role_to": ["administrator", "editor"], - "update": True, - "partial_update": True, - }, - }, - { - "id": str(access_editor.id), - "user": None, - "team": "editors", - "role": "editor", - "abilities": { - "destroy": True, - "retrieve": True, - "set_role_to": ["administrator", "reader"], - "update": True, - "partial_update": True, - }, - }, - { - "id": str(access_administrator.id), - "user": None, - "team": "administrators", - "role": "administrator", - "abilities": { - "destroy": True, - "retrieve": True, - "set_role_to": ["editor", "reader"], - "update": True, - "partial_update": True, - }, - }, - { - "id": str(access_owner.id), - "user": None, - "team": "owners", - "role": "owner", - "abilities": { - "destroy": False, - "retrieve": True, - "set_role_to": [], - "update": False, - "partial_update": False, - }, - }, - { - "id": str(other_access.id), - "user": None, - "team": other_access.team, - "role": other_access.role, - "abilities": other_access.get_abilities(user), - }, - ], - key=lambda x: x["id"], - ) - assert response.json() == { - "id": str(template.id), - "title": template.title, - "abilities": template.get_abilities(user), - "is_public": False, - "code": template.code, - "css": template.css, - } - - -@pytest.mark.parametrize( - "teams", - [ - ["owners"], - ["owners", "administrators"], - ["members", "administrators", "owners"], - ["unknown", "owners"], - ], -) -def test_api_templates_retrieve_authenticated_related_team_owners( - teams, mock_user_teams -): - """ - Authenticated users should be allowed to retrieve a template to which they - are related via a team whatever the role and see all its accesses. - """ - mock_user_teams.return_value = teams - - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template = factories.TemplateFactory(is_public=False) - - access_reader = factories.TeamTemplateAccessFactory( - template=template, team="readers", role="reader" - ) - access_editor = factories.TeamTemplateAccessFactory( - template=template, team="editors", role="editor" - ) - access_administrator = factories.TeamTemplateAccessFactory( - template=template, team="administrators", role="administrator" - ) - access_owner = factories.TeamTemplateAccessFactory( - template=template, team="owners", role="owner" - ) - other_access = factories.TeamTemplateAccessFactory(template=template) - factories.TeamTemplateAccessFactory() - - response = client.get(f"/api/v1.0/templates/{template.id!s}/") - - assert response.status_code == 200 - content = response.json() - assert sorted(content.pop("accesses"), key=lambda x: x["id"]) == sorted( - [ - { - "id": str(access_reader.id), - "user": None, - "team": "readers", - "role": "reader", - "abilities": { - "destroy": True, - "retrieve": True, - "set_role_to": ["owner", "administrator", "editor"], - "update": True, - "partial_update": True, - }, - }, - { - "id": str(access_editor.id), - "user": None, - "team": "editors", - "role": "editor", - "abilities": { - "destroy": True, - "retrieve": True, - "set_role_to": ["owner", "administrator", "reader"], - "update": True, - "partial_update": True, - }, - }, - { - "id": str(access_administrator.id), - "user": None, - "team": "administrators", - "role": "administrator", - "abilities": { - "destroy": True, - "retrieve": True, - "set_role_to": ["owner", "editor", "reader"], - "update": True, - "partial_update": True, - }, - }, - { - "id": str(access_owner.id), - "user": None, - "team": "owners", - "role": "owner", - "abilities": { - # editable only if there is another owner role than the user's team... - "destroy": other_access.role == "owner", - "retrieve": True, - "set_role_to": ["administrator", "editor", "reader"] - if other_access.role == "owner" - else [], - "update": other_access.role == "owner", - "partial_update": other_access.role == "owner", - }, - }, - { - "id": str(other_access.id), - "user": None, - "team": other_access.team, - "role": other_access.role, - "abilities": other_access.get_abilities(user), - }, - ], - key=lambda x: x["id"], - ) - assert response.json() == { - "id": str(template.id), - "title": template.title, - "abilities": template.get_abilities(user), - "is_public": False, - "code": template.code, - "css": template.css, - } diff --git a/src/backend/core/tests/templates/test_api_templates_update.py b/src/backend/core/tests/templates/test_api_templates_update.py deleted file mode 100644 index a2040293..00000000 --- a/src/backend/core/tests/templates/test_api_templates_update.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Tests for Templates API endpoint in impress's core app: update -""" - -import pytest -from rest_framework.test import APIClient - -from core import factories -from core.api import serializers - -pytestmark = pytest.mark.django_db - - -def test_api_templates_update_anonymous(): - """Anonymous users should not be allowed to update a template.""" - template = factories.TemplateFactory() - - new_template_values = serializers.TemplateSerializer( - instance=factories.TemplateFactory() - ).data - response = APIClient().put( - f"/api/v1.0/templates/{template.id!s}/", - new_template_values, - format="json", - ) - assert response.status_code == 401 - - -def test_api_templates_update_not_implemented(): - """ - Authenticated users should not be allowed to update a template. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - template = factories.TemplateFactory(users=[(user, "owner")]) - - new_template_values = serializers.TemplateSerializer( - instance=factories.TemplateFactory() - ).data - - response = client.put( - f"/api/v1.0/templates/{template.id!s}/", new_template_values, format="json" - ) - - assert response.status_code == 405 - - response = client.patch( - f"/api/v1.0/templates/{template.id!s}/", new_template_values, format="json" - ) - - assert response.status_code == 405 diff --git a/src/backend/core/tests/test_models_template_accesses.py b/src/backend/core/tests/test_models_template_accesses.py deleted file mode 100644 index 70a24164..00000000 --- a/src/backend/core/tests/test_models_template_accesses.py +++ /dev/null @@ -1,419 +0,0 @@ -""" -Unit tests for the TemplateAccess model -""" - -from django.contrib.auth.models import AnonymousUser -from django.core.exceptions import ValidationError - -import pytest - -from core import factories - -pytestmark = pytest.mark.django_db - - -def test_models_template_accesses_str(): - """ - The str representation should include user email, template title and role. - """ - user = factories.UserFactory(email="david.bowman@example.com") - access = factories.UserTemplateAccessFactory( - role="reader", - user=user, - template__title="admins", - ) - assert str(access) == "david.bowman@example.com is reader in template admins" - - -def test_models_template_accesses_unique_user(): - """Template accesses should be unique for a given couple of user and template.""" - access = factories.UserTemplateAccessFactory() - - with pytest.raises( - ValidationError, - match="This user is already in this template.", - ): - factories.UserTemplateAccessFactory(user=access.user, template=access.template) - - -def test_models_template_accesses_several_empty_teams(): - """A template can have several template accesses with an empty team.""" - access = factories.UserTemplateAccessFactory() - factories.UserTemplateAccessFactory(template=access.template) - - -def test_models_template_accesses_unique_team(): - """Template accesses should be unique for a given couple of team and template.""" - access = factories.TeamTemplateAccessFactory() - - with pytest.raises( - ValidationError, - match="This team is already in this template.", - ): - factories.TeamTemplateAccessFactory(team=access.team, template=access.template) - - -def test_models_template_accesses_several_null_users(): - """A template can have several template accesses with a null user.""" - access = factories.TeamTemplateAccessFactory() - factories.TeamTemplateAccessFactory(template=access.template) - - -def test_models_template_accesses_user_and_team_set(): - """User and team can't both be set on a template access.""" - with pytest.raises( - ValidationError, - match="Either user or team must be set, not both.", - ): - factories.UserTemplateAccessFactory(team="my-team") - - -def test_models_template_accesses_user_and_team_empty(): - """User and team can't both be empty on a template access.""" - with pytest.raises( - ValidationError, - match="Either user or team must be set, not both.", - ): - factories.UserTemplateAccessFactory(user=None) - - -# get_abilities - - -def test_models_template_access_get_abilities_anonymous(): - """Check abilities returned for an anonymous user.""" - access = factories.UserTemplateAccessFactory() - abilities = access.get_abilities(AnonymousUser()) - assert abilities == { - "destroy": False, - "retrieve": False, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -def test_models_template_access_get_abilities_authenticated(): - """Check abilities returned for an authenticated user.""" - access = factories.UserTemplateAccessFactory() - user = factories.UserFactory() - abilities = access.get_abilities(user) - assert abilities == { - "destroy": False, - "retrieve": False, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -# - for owner - - -def test_models_template_access_get_abilities_for_owner_of_self_allowed(): - """ - Check abilities of self access for the owner of a template when - there is more than one owner left. - """ - access = factories.UserTemplateAccessFactory(role="owner") - factories.UserTemplateAccessFactory(template=access.template, role="owner") - abilities = access.get_abilities(access.user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "partial_update": True, - "set_role_to": ["administrator", "editor", "reader"], - } - - -def test_models_template_access_get_abilities_for_owner_of_self_last(): - """ - Check abilities of self access for the owner of a template when there is only one owner left. - """ - access = factories.UserTemplateAccessFactory(role="owner") - abilities = access.get_abilities(access.user) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -def test_models_template_access_get_abilities_for_owner_of_owner(): - """Check abilities of owner access for the owner of a template.""" - access = factories.UserTemplateAccessFactory(role="owner") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="owner" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "partial_update": True, - "set_role_to": ["administrator", "editor", "reader"], - } - - -def test_models_template_access_get_abilities_for_owner_of_administrator(): - """Check abilities of administrator access for the owner of a template.""" - access = factories.UserTemplateAccessFactory(role="administrator") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="owner" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "partial_update": True, - "set_role_to": ["owner", "editor", "reader"], - } - - -def test_models_template_access_get_abilities_for_owner_of_editor(): - """Check abilities of editor access for the owner of a template.""" - access = factories.UserTemplateAccessFactory(role="editor") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="owner" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "partial_update": True, - "set_role_to": ["owner", "administrator", "reader"], - } - - -def test_models_template_access_get_abilities_for_owner_of_reader(): - """Check abilities of reader access for the owner of a template.""" - access = factories.UserTemplateAccessFactory(role="reader") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="owner" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "partial_update": True, - "set_role_to": ["owner", "administrator", "editor"], - } - - -# - for administrator - - -def test_models_template_access_get_abilities_for_administrator_of_owner(): - """Check abilities of owner access for the administrator of a template.""" - access = factories.UserTemplateAccessFactory(role="owner") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="administrator" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -def test_models_template_access_get_abilities_for_administrator_of_administrator(): - """Check abilities of administrator access for the administrator of a template.""" - access = factories.UserTemplateAccessFactory(role="administrator") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="administrator" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "partial_update": True, - "set_role_to": ["editor", "reader"], - } - - -def test_models_template_access_get_abilities_for_administrator_of_editor(): - """Check abilities of editor access for the administrator of a template.""" - access = factories.UserTemplateAccessFactory(role="editor") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="administrator" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "partial_update": True, - "set_role_to": ["administrator", "reader"], - } - - -def test_models_template_access_get_abilities_for_administrator_of_reader(): - """Check abilities of reader access for the administrator of a template.""" - access = factories.UserTemplateAccessFactory(role="reader") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="administrator" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "partial_update": True, - "set_role_to": ["administrator", "editor"], - } - - -# - For editor - - -def test_models_template_access_get_abilities_for_editor_of_owner(): - """Check abilities of owner access for the editor of a template.""" - access = factories.UserTemplateAccessFactory(role="owner") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="editor" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -def test_models_template_access_get_abilities_for_editor_of_administrator(): - """Check abilities of administrator access for the editor of a template.""" - access = factories.UserTemplateAccessFactory(role="administrator") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="editor" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -def test_models_template_access_get_abilities_for_editor_of_editor_user( - django_assert_num_queries, -): - """Check abilities of editor access for the editor of a template.""" - access = factories.UserTemplateAccessFactory(role="editor") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="editor" - ).user - - with django_assert_num_queries(1): - abilities = access.get_abilities(user) - - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -# - For reader - - -def test_models_template_access_get_abilities_for_reader_of_owner(): - """Check abilities of owner access for the reader of a template.""" - access = factories.UserTemplateAccessFactory(role="owner") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="reader" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -def test_models_template_access_get_abilities_for_reader_of_administrator(): - """Check abilities of administrator access for the reader of a template.""" - access = factories.UserTemplateAccessFactory(role="administrator") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="reader" - ).user - abilities = access.get_abilities(user) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -def test_models_template_access_get_abilities_for_reader_of_reader_user( - django_assert_num_queries, -): - """Check abilities of reader access for the reader of a template.""" - access = factories.UserTemplateAccessFactory(role="reader") - factories.UserTemplateAccessFactory(template=access.template) # another one - user = factories.UserTemplateAccessFactory( - template=access.template, role="reader" - ).user - - with django_assert_num_queries(1): - abilities = access.get_abilities(user) - - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } - - -def test_models_template_access_get_abilities_preset_role(django_assert_num_queries): - """No query is done if the role is preset, e.g., with a query annotation.""" - access = factories.UserTemplateAccessFactory(role="reader") - user = factories.UserTemplateAccessFactory( - template=access.template, role="reader" - ).user - access.user_roles = ["reader"] - - with django_assert_num_queries(0): - abilities = access.get_abilities(user) - - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "partial_update": False, - "set_role_to": [], - } diff --git a/src/backend/core/tests/test_models_templates.py b/src/backend/core/tests/test_models_templates.py deleted file mode 100644 index 95f8fbde..00000000 --- a/src/backend/core/tests/test_models_templates.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -Unit tests for the Template model -""" - -from django.contrib.auth.models import AnonymousUser -from django.core.exceptions import ValidationError - -import pytest - -from core import factories, models - -pytestmark = pytest.mark.django_db - - -def test_models_templates_str(): - """The str representation should be the title of the template.""" - template = factories.TemplateFactory(title="admins") - assert str(template) == "admins" - - -def test_models_templates_id_unique(): - """The "id" field should be unique.""" - template = factories.TemplateFactory() - with pytest.raises(ValidationError, match="Template with this Id already exists."): - factories.TemplateFactory(id=template.id) - - -def test_models_templates_title_null(): - """The "title" field should not be null.""" - with pytest.raises(ValidationError, match="This field cannot be null."): - models.Template.objects.create(title=None) - - -def test_models_templates_title_empty(): - """The "title" field should not be empty.""" - with pytest.raises(ValidationError, match="This field cannot be blank."): - models.Template.objects.create(title="") - - -def test_models_templates_title_max_length(): - """The "title" field should be 100 characters maximum.""" - factories.TemplateFactory(title="a" * 255) - with pytest.raises( - ValidationError, - match=r"Ensure this value has at most 255 characters \(it has 256\)\.", - ): - factories.TemplateFactory(title="a" * 256) - - -# get_abilities - - -def test_models_templates_get_abilities_anonymous_public(): - """Check abilities returned for an anonymous user if the template is public.""" - template = factories.TemplateFactory(is_public=True) - abilities = template.get_abilities(AnonymousUser()) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "accesses_manage": False, - "partial_update": False, - "generate_document": True, - } - - -def test_models_templates_get_abilities_anonymous_not_public(): - """Check abilities returned for an anonymous user if the template is private.""" - template = factories.TemplateFactory(is_public=False) - abilities = template.get_abilities(AnonymousUser()) - assert abilities == { - "destroy": False, - "retrieve": False, - "update": False, - "accesses_manage": False, - "partial_update": False, - "generate_document": False, - } - - -def test_models_templates_get_abilities_authenticated_public(): - """Check abilities returned for an authenticated user if the user is public.""" - template = factories.TemplateFactory(is_public=True) - abilities = template.get_abilities(factories.UserFactory()) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "accesses_manage": False, - "partial_update": False, - "generate_document": True, - } - - -def test_models_templates_get_abilities_authenticated_not_public(): - """Check abilities returned for an authenticated user if the template is private.""" - template = factories.TemplateFactory(is_public=False) - abilities = template.get_abilities(factories.UserFactory()) - assert abilities == { - "destroy": False, - "retrieve": False, - "update": False, - "accesses_manage": False, - "partial_update": False, - "generate_document": False, - } - - -def test_models_templates_get_abilities_owner(): - """Check abilities returned for the owner of a template.""" - user = factories.UserFactory() - access = factories.UserTemplateAccessFactory(role="owner", user=user) - abilities = access.template.get_abilities(access.user) - assert abilities == { - "destroy": True, - "retrieve": True, - "update": True, - "accesses_manage": True, - "partial_update": True, - "generate_document": True, - } - - -def test_models_templates_get_abilities_administrator(): - """Check abilities returned for the administrator of a template.""" - access = factories.UserTemplateAccessFactory(role="administrator") - abilities = access.template.get_abilities(access.user) - assert abilities == { - "destroy": False, - "retrieve": True, - "update": True, - "accesses_manage": True, - "partial_update": True, - "generate_document": True, - } - - -def test_models_templates_get_abilities_editor_user(django_assert_num_queries): - """Check abilities returned for the editor of a template.""" - access = factories.UserTemplateAccessFactory(role="editor") - - with django_assert_num_queries(1): - abilities = access.template.get_abilities(access.user) - - assert abilities == { - "destroy": False, - "retrieve": True, - "update": True, - "accesses_manage": False, - "partial_update": True, - "generate_document": True, - } - - -def test_models_templates_get_abilities_reader_user(django_assert_num_queries): - """Check abilities returned for the reader of a template.""" - access = factories.UserTemplateAccessFactory(role="reader") - - with django_assert_num_queries(1): - abilities = access.template.get_abilities(access.user) - - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "accesses_manage": False, - "partial_update": False, - "generate_document": True, - } - - -def test_models_templates_get_abilities_preset_role(django_assert_num_queries): - """No query is done if the role is preset e.g. with query annotation.""" - access = factories.UserTemplateAccessFactory(role="reader") - access.template.user_roles = ["reader"] - - with django_assert_num_queries(0): - abilities = access.template.get_abilities(access.user) - - assert abilities == { - "destroy": False, - "retrieve": True, - "update": False, - "accesses_manage": False, - "partial_update": False, - "generate_document": True, - } diff --git a/src/backend/core/urls.py b/src/backend/core/urls.py index a24ebc99..03593f26 100644 --- a/src/backend/core/urls.py +++ b/src/backend/core/urls.py @@ -10,7 +10,6 @@ from core.api import viewsets # - Main endpoints router = DefaultRouter() -router.register("templates", viewsets.TemplateViewSet, basename="templates") router.register("documents", viewsets.DocumentViewSet, basename="documents") router.register("users", viewsets.UserViewSet, basename="users") diff --git a/src/backend/demo/data/template/code.txt b/src/backend/demo/data/template/code.txt deleted file mode 100644 index 229af08d..00000000 --- a/src/backend/demo/data/template/code.txt +++ /dev/null @@ -1,2 +0,0 @@ - -
\ No newline at end of file diff --git a/src/backend/demo/data/template/css.txt b/src/backend/demo/data/template/css.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/backend/demo/management/commands/create_demo.py b/src/backend/demo/management/commands/create_demo.py index c3a22752..695e2a4a 100644 --- a/src/backend/demo/management/commands/create_demo.py +++ b/src/backend/demo/management/commands/create_demo.py @@ -216,29 +216,6 @@ def create_demo(stdout): queue.flush() - with Timeit(stdout, "Creating Template"): - with open( - file="demo/data/template/code.txt", mode="r", encoding="utf-8" - ) as text_file: - code_data = text_file.read() - - with open( - file="demo/data/template/css.txt", mode="r", encoding="utf-8" - ) as text_file: - css_data = text_file.read() - - queue.push( - models.Template( - id="baca9e2a-59fb-42ef-b5c6-6f6b05637111", - title="Demo Template", - description="This is the demo template", - code=code_data, - css=css_data, - is_public=True, - ) - ) - queue.flush() - class Command(BaseCommand): """A management command to create a demo database.""" diff --git a/src/backend/demo/tests/test_commands_create_demo.py b/src/backend/demo/tests/test_commands_create_demo.py index 6bd19e11..52233301 100644 --- a/src/backend/demo/tests/test_commands_create_demo.py +++ b/src/backend/demo/tests/test_commands_create_demo.py @@ -25,7 +25,6 @@ def test_commands_create_demo(): """The create_demo management command should create objects as expected.""" call_command("create_demo") - assert models.Template.objects.count() == 1 assert models.User.objects.count() >= 10 assert models.Document.objects.count() >= 10 assert models.DocumentAccess.objects.count() > 10 diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index c6e29efb..3acac6c3 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -406,16 +406,6 @@ class Base(Configuration): environ_name="API_DOCUMENT_ACCESS_THROTTLE_RATE", environ_prefix=None, ), - "template": values.Value( - default="30/minute", - environ_name="API_TEMPLATE_THROTTLE_RATE", - environ_prefix=None, - ), - "template_access": values.Value( - default="30/minute", - environ_name="API_TEMPLATE_ACCESS_THROTTLE_RATE", - environ_prefix=None, - ), "invitation": values.Value( default="60/minute", environ_name="API_INVITATION_THROTTLE_RATE", diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index d532906d..edc9640f 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", ] -description = "An application to print markdown to pdf from a set of managed templates." +description = "Docs is a collaborative text editor designed to address common challenges in knowledge building and sharing." keywords = ["Django", "Contacts", "Templates", "RBAC"] license = { file = "LICENSE" } readme = "README.md"