🔥(backend) remove all code related to template
The template feature is removed. Migration created to drop related tables. Files modified: - viewsets - serializers - models - admin - factories - urls - tests - demo data
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -10,6 +10,12 @@ and this project adheres to
|
|||||||
|
|
||||||
- ✨(frontend) integrate configurable Waffle #1795
|
- ✨(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
|
### Fixed
|
||||||
|
|
||||||
- ✅(e2e) fix e2e test for other browsers #1799
|
- ✅(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) fix emojipicker closing in tree #1808
|
||||||
- 🐛(frontend) display children in favorite #1782
|
- 🐛(frontend) display children in favorite #1782
|
||||||
|
|
||||||
### Changed
|
### Removed
|
||||||
|
|
||||||
- ♿(frontend) improve accessibility:
|
- 🔥(project) remove all code related to template #1780
|
||||||
- ♿️(frontend) fix subdoc opening and emoji pick focus #1745
|
|
||||||
- ✨(backend) add field for button label in email template #1817
|
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- 🔒️(trivy) fix vulnerability about jaraco.context #1806
|
- 🔒️(trivy) fix vulnerability about jaraco.context #1806
|
||||||
|
|
||||||
|
|
||||||
## [4.4.0] - 2026-01-13
|
## [4.4.0] - 2026-01-13
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -9,14 +9,6 @@ from treebeard.admin import TreeAdmin
|
|||||||
from . import models
|
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)
|
@admin.register(models.User)
|
||||||
class UserAdmin(auth_admin.UserAdmin):
|
class UserAdmin(auth_admin.UserAdmin):
|
||||||
"""Admin class for the User model"""
|
"""Admin class for the User model"""
|
||||||
@@ -69,7 +61,6 @@ class UserAdmin(auth_admin.UserAdmin):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
inlines = (TemplateAccessInline,)
|
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
"sub",
|
"sub",
|
||||||
@@ -104,15 +95,8 @@ class UserAdmin(auth_admin.UserAdmin):
|
|||||||
search_fields = ("id", "sub", "admin_email", "email", "full_name")
|
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):
|
class DocumentAccessInline(admin.TabularInline):
|
||||||
"""Inline admin class for template accesses."""
|
"""Inline admin class for document accesses."""
|
||||||
|
|
||||||
autocomplete_fields = ["user"]
|
autocomplete_fields = ["user"]
|
||||||
model = models.DocumentAccess
|
model = models.DocumentAccess
|
||||||
|
|||||||
@@ -98,10 +98,10 @@ class CanCreateInvitationPermission(permissions.BasePermission):
|
|||||||
|
|
||||||
|
|
||||||
class ResourceWithAccessPermission(permissions.BasePermission):
|
class ResourceWithAccessPermission(permissions.BasePermission):
|
||||||
"""A permission class for templates and invitations."""
|
"""A permission class for invitations."""
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
"""check create permission for templates."""
|
"""check create permission."""
|
||||||
return request.user.is_authenticated or view.action != "create"
|
return request.user.is_authenticated or view.action != "create"
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
|
|||||||
@@ -59,30 +59,6 @@ class UserLightSerializer(UserSerializer):
|
|||||||
read_only_fields = ["full_name", "short_name"]
|
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):
|
class ListDocumentSerializer(serializers.ModelSerializer):
|
||||||
"""Serialize documents with limited fields for display in lists."""
|
"""Serialize documents with limited fields for display in lists."""
|
||||||
|
|
||||||
@@ -660,52 +636,6 @@ class FileUploadSerializer(serializers.Serializer):
|
|||||||
return attrs
|
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):
|
class InvitationSerializer(serializers.ModelSerializer):
|
||||||
"""Serialize invitations."""
|
"""Serialize invitations."""
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
class InvitationViewset(
|
||||||
drf.mixins.CreateModelMixin,
|
drf.mixins.CreateModelMixin,
|
||||||
drf.mixins.ListModelMixin,
|
drf.mixins.ListModelMixin,
|
||||||
|
|||||||
@@ -53,15 +53,6 @@ class UserFactory(factory.django.DjangoModelFactory):
|
|||||||
if create and (extracted is True):
|
if create and (extracted is True):
|
||||||
UserDocumentAccessFactory(user=self, role="owner")
|
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):
|
class ParentNodeFactory(factory.declarations.ParameteredAttribute):
|
||||||
"""Custom factory attribute for setting the parent node."""
|
"""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])
|
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):
|
class InvitationFactory(factory.django.DjangoModelFactory):
|
||||||
"""A factory to create invitations for a user"""
|
"""A factory to create invitations for a user"""
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1430,163 +1430,6 @@ class Reaction(BaseModel):
|
|||||||
return f"Reaction {self.emoji} on comment {self.comment.id}"
|
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):
|
class Invitation(BaseModel):
|
||||||
"""User invitation to a document."""
|
"""User invitation to a document."""
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Generate Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Generate Document</h2>
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
|
||||||
<button type="submit">Generate PDF</button>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Custom template tags for the core application of People."""
|
"""Custom template tags for the core application of Docs."""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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": [],
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ from core.api import viewsets
|
|||||||
|
|
||||||
# - Main endpoints
|
# - Main endpoints
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register("templates", viewsets.TemplateViewSet, basename="templates")
|
|
||||||
router.register("documents", viewsets.DocumentViewSet, basename="documents")
|
router.register("documents", viewsets.DocumentViewSet, basename="documents")
|
||||||
router.register("users", viewsets.UserViewSet, basename="users")
|
router.register("users", viewsets.UserViewSet, basename="users")
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
<img width="200" src="http://localhost:3000/assets/logo-gouv.png" />
|
|
||||||
<br/>
|
|
||||||
@@ -216,29 +216,6 @@ def create_demo(stdout):
|
|||||||
|
|
||||||
queue.flush()
|
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):
|
class Command(BaseCommand):
|
||||||
"""A management command to create a demo database."""
|
"""A management command to create a demo database."""
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ def test_commands_create_demo():
|
|||||||
"""The create_demo management command should create objects as expected."""
|
"""The create_demo management command should create objects as expected."""
|
||||||
call_command("create_demo")
|
call_command("create_demo")
|
||||||
|
|
||||||
assert models.Template.objects.count() == 1
|
|
||||||
assert models.User.objects.count() >= 10
|
assert models.User.objects.count() >= 10
|
||||||
assert models.Document.objects.count() >= 10
|
assert models.Document.objects.count() >= 10
|
||||||
assert models.DocumentAccess.objects.count() > 10
|
assert models.DocumentAccess.objects.count() > 10
|
||||||
|
|||||||
@@ -406,16 +406,6 @@ class Base(Configuration):
|
|||||||
environ_name="API_DOCUMENT_ACCESS_THROTTLE_RATE",
|
environ_name="API_DOCUMENT_ACCESS_THROTTLE_RATE",
|
||||||
environ_prefix=None,
|
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(
|
"invitation": values.Value(
|
||||||
default="60/minute",
|
default="60/minute",
|
||||||
environ_name="API_INVITATION_THROTTLE_RATE",
|
environ_name="API_INVITATION_THROTTLE_RATE",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.12",
|
"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"]
|
keywords = ["Django", "Contacts", "Templates", "RBAC"]
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
Reference in New Issue
Block a user