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
-
-
-
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"