Fix Editor filter and view permissions

This commit is contained in:
IRBorisov 2024-06-03 01:17:27 +03:00
parent f4c2731b72
commit 93d56ef4fa
9 changed files with 30 additions and 11 deletions

View File

@ -1,5 +1,5 @@
''' Custom Permission classes. ''' Custom Permission classes.
Hierarchy: Anonymous -> User -> Editor -> Owner -> Admin Hierarchy: Anyone -> User -> Editor -> Owner -> Admin
''' '''
from typing import Any, cast from typing import Any, cast
@ -66,6 +66,16 @@ class ItemEditor(ItemOwner):
return super().has_object_permission(request, view, obj) return super().has_object_permission(request, view, obj)
class ItemAnyone(ItemEditor):
''' Item permission: Anyone if public. '''
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool:
item = _extract_item(obj)
if item.access_policy == m.AccessPolicy.PUBLIC:
return True
return super().has_object_permission(request, view, obj)
class EditorMixin(APIView): class EditorMixin(APIView):
''' Editor permissions mixin for API views. ''' ''' Editor permissions mixin for API views. '''

View File

@ -36,7 +36,9 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
'load_trs', 'cst_create', 'cst_delete_multiple', 'load_trs', 'cst_create', 'cst_delete_multiple',
'reset_aliases', 'cst_rename', 'cst_substitute' 'reset_aliases', 'cst_rename', 'cst_substitute'
]: ]:
permission_list = [permissions.ItemOwner] permission_list = [permissions.ItemEditor]
elif self.action in ['contents', 'details', 'export_trs', 'resolve', 'check']:
permission_list = [permissions.ItemAnyone]
else: else:
permission_list = [permissions.Anyone] permission_list = [permissions.Anyone]
return [permission() for permission in permission_list] return [permission() for permission in permission_list]

View File

@ -3,7 +3,7 @@ from django.contrib.auth import authenticate
from django.contrib.auth.password_validation import validate_password from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers from rest_framework import serializers
from apps.rsform.models import Subscription from apps.rsform.models import Editor, Subscription
from . import messages as msg from . import messages as msg
from . import models from . import models
@ -69,14 +69,16 @@ class AuthSerializer(serializers.Serializer):
'id': None, 'id': None,
'username': '', 'username': '',
'is_staff': False, 'is_staff': False,
'subscriptions': [] 'subscriptions': [],
'editor': []
} }
else: else:
return { return {
'id': instance.pk, 'id': instance.pk,
'username': instance.username, 'username': instance.username,
'is_staff': instance.is_staff, 'is_staff': instance.is_staff,
'subscriptions': [sub.item.pk for sub in Subscription.objects.filter(user=instance)] 'subscriptions': [sub.item.pk for sub in Subscription.objects.filter(user=instance)],
'editor': [edit.item.pk for edit in Editor.objects.filter(editor=instance)]
} }

View File

@ -44,6 +44,7 @@ class TestUserAPIViews(EndpointTester):
self.assertEqual(response.data['username'], self.user.username) self.assertEqual(response.data['username'], self.user.username)
self.assertEqual(response.data['is_staff'], self.user.is_staff) self.assertEqual(response.data['is_staff'], self.user.is_staff)
self.assertEqual(response.data['subscriptions'], []) self.assertEqual(response.data['subscriptions'], [])
self.assertEqual(response.data['editor'], [])
self.logout() self.logout()
response = self.executeOK() response = self.executeOK()
@ -51,6 +52,7 @@ class TestUserAPIViews(EndpointTester):
self.assertEqual(response.data['username'], '') self.assertEqual(response.data['username'], '')
self.assertEqual(response.data['is_staff'], False) self.assertEqual(response.data['is_staff'], False)
self.assertEqual(response.data['subscriptions'], []) self.assertEqual(response.data['subscriptions'], [])
self.assertEqual(response.data['editor'], [])
class TestUserUserProfileAPIView(EndpointTester): class TestUserUserProfileAPIView(EndpointTester):

View File

@ -112,8 +112,10 @@ class UpdatePassword(views.APIView):
if serializer.is_valid(): if serializer.is_valid():
old_password = serializer.data.get("old_password") old_password = serializer.data.get("old_password")
if not self.object.check_password(old_password): if not self.object.check_password(old_password):
return Response({"old_password": ["Wrong password."]}, return Response(
status=c.HTTP_400_BAD_REQUEST) {"old_password": ["Wrong password."]},
status=c.HTTP_400_BAD_REQUEST
)
# Note: set_password also hashes the password that the user will get # Note: set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password")) self.object.set_password(serializer.data.get("new_password"))
self.object.save() self.object.save()

View File

@ -172,7 +172,7 @@ DATABASES = {
SPECTACULAR_SETTINGS = { SPECTACULAR_SETTINGS = {
'TITLE': 'ConceptPortal API', 'TITLE': 'ConceptPortal API',
'DESCRIPTION': 'Портал для работы с экспликациями концептуальных схем', 'DESCRIPTION': 'Портал для работы с экспликациями концептуальных схем',
'VERSION': '0.1.1', 'VERSION': '0.1.2',
'SERVE_INCLUDE_SCHEMA': False, 'SERVE_INCLUDE_SCHEMA': False,
'COMPONENT_SPLIT_PATCH': True, 'COMPONENT_SPLIT_PATCH': True,

View File

@ -81,7 +81,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
result = result.filter(item => filter.isSubscribed == user?.subscriptions.includes(item.id)); result = result.filter(item => filter.isSubscribed == user?.subscriptions.includes(item.id));
} }
if (filter.isEditor !== undefined) { if (filter.isEditor !== undefined) {
// TODO: load editors from backend result = result.filter(item => filter.isEditor == user?.editor.includes(item.id));
} }
if (filter.query) { if (filter.query) {
result = result.filter(item => matchLibraryItem(item, filter.query!)); result = result.filter(item => matchLibraryItem(item, filter.query!));

View File

@ -27,6 +27,7 @@ export interface IUser {
*/ */
export interface ICurrentUser extends Pick<IUser, 'id' | 'username' | 'is_staff'> { export interface ICurrentUser extends Pick<IUser, 'id' | 'username' | 'is_staff'> {
subscriptions: LibraryItemID[]; subscriptions: LibraryItemID[];
editor: LibraryItemID[];
} }
/** /**

View File

@ -262,7 +262,7 @@ export function labelLocationHead(head: LocationHead): string {
switch (head) { switch (head) {
case LocationHead.USER: return 'личные (/U)'; case LocationHead.USER: return 'личные (/U)';
case LocationHead.COMMON: return 'общие (/S)'; case LocationHead.COMMON: return 'общие (/S)';
case LocationHead.LIBRARY: return 'неизменные (/L)'; case LocationHead.LIBRARY: return 'примеры (/L)';
case LocationHead.PROJECTS: return 'проекты (/P)'; case LocationHead.PROJECTS: return 'проекты (/P)';
} }
} }
@ -275,7 +275,7 @@ export function describeLocationHead(head: LocationHead): string {
switch (head) { switch (head) {
case LocationHead.USER: return 'Личные схемы пользователя'; case LocationHead.USER: return 'Личные схемы пользователя';
case LocationHead.COMMON: return 'Рабочий каталог публичных схем'; case LocationHead.COMMON: return 'Рабочий каталог публичных схем';
case LocationHead.LIBRARY: return 'Каталог неизменных схем'; case LocationHead.LIBRARY: return 'Каталог неизменных схем-примеров';
case LocationHead.PROJECTS: return 'Рабочий каталог проектных схем'; case LocationHead.PROJECTS: return 'Рабочий каталог проектных схем';
} }
} }