Refactoring: preparing backend for oss pt1

This commit is contained in:
Ivan 2024-07-17 12:56:01 +03:00
parent aed899ba70
commit da7113ce7e
47 changed files with 634 additions and 60 deletions

View File

@ -11,7 +11,9 @@
"--multi-line",
"3",
"--project",
"apps"
"apps",
"--project",
"shared"
],
"autopep8.args": [
"--max-line-length",

View File

@ -69,6 +69,7 @@ This readme file is used mostly to document project dependencies
- Backticks
- Svg Preview
- TODO Highlight v2
- Prettier
</pre>
</details>
<details>
@ -114,8 +115,9 @@ This readme file is used mostly to document project dependencies
<pre>
- Pylance
- Pylint
- Django
- autopep8
- isort
- Django
- SQLite
</pre>
</details>

View File

View File

@ -0,0 +1,14 @@
''' Admin view: OperationSchema. '''
from django.contrib import admin
from . import models
class OperationAdmin(admin.ModelAdmin):
''' Admin model: Operation. '''
ordering = ['oss']
list_display = ['oss', 'operation_type', 'result', 'alias', 'title', 'comment', 'position_x', 'position_y']
search_fields = ['operation_type', 'title', 'alias']
admin.site.register(models.Operation, OperationAdmin)

View File

@ -0,0 +1,8 @@
''' Application: Operation Schema. '''
from django.apps import AppConfig
class RsformConfig(AppConfig):
''' Application config. '''
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.oss'

View File

@ -0,0 +1,69 @@
# Generated by Django 5.0.7 on 2024-07-17 09:51
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('rsform', '0008_alter_libraryitem_item_type'),
]
operations = [
migrations.CreateModel(
name='Operation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('operation_type', models.CharField(choices=[
('input', 'Input'), ('synthesis', 'Synthesis')], default='input', max_length=10, verbose_name='Тип')),
('alias', models.CharField(blank=True, max_length=255, verbose_name='Шифр')),
('title', models.TextField(blank=True, verbose_name='Название')),
('comment', models.TextField(blank=True, verbose_name='Комментарий')),
('position_x', models.FloatField(default=0, verbose_name='Положение по горизонтали')),
('position_y', models.FloatField(default=0, verbose_name='Положение по вертикали')),
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='items', to='rsform.libraryitem', verbose_name='Схема синтеза')),
('result', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='producer', to='rsform.libraryitem', verbose_name='Связанная КС')),
],
options={
'verbose_name': 'Операция',
'verbose_name_plural': 'Операции',
},
),
migrations.CreateModel(
name='SynthesisSubstitution',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('transfer_term', models.BooleanField(default=False, verbose_name='Перенос термина')),
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
to='oss.operation', verbose_name='Операция')),
('original', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='as_original', to='rsform.constituenta', verbose_name='Удаляемая конституента')),
('substitution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='as_substitute', to='rsform.constituenta', verbose_name='Замещающая конституента')),
],
options={
'verbose_name': 'Отождествление синтеза',
'verbose_name_plural': 'Таблицы отождествлений',
},
),
migrations.CreateModel(
name='Argument',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('argument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='descendants', to='oss.operation', verbose_name='Аргумент')),
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='arguments', to='oss.operation', verbose_name='Операция')),
],
options={
'verbose_name': 'Аргумент',
'verbose_name_plural': 'Аргументы операций',
'unique_together': {('operation', 'argument')},
},
),
]

View File

@ -0,0 +1,27 @@
''' Models: Operation Argument in OSS. '''
from django.db.models import CASCADE, ForeignKey, Model
class Argument(Model):
''' Operation Argument.'''
operation: ForeignKey = ForeignKey(
verbose_name='Операция',
to='oss.Operation',
on_delete=CASCADE,
related_name='arguments'
)
argument: ForeignKey = ForeignKey(
verbose_name='Аргумент',
to='oss.Operation',
on_delete=CASCADE,
related_name='descendants'
)
class Meta:
''' Model metadata. '''
verbose_name = 'Аргумент'
verbose_name_plural = 'Аргументы операций'
unique_together = [['operation', 'argument']]
def __str__(self) -> str:
return f'{self.argument.pk} -> {self.operation.pk}'

View File

@ -0,0 +1,71 @@
''' Models: Operation in OSS. '''
from django.db.models import (
CASCADE,
SET_NULL,
CharField,
FloatField,
ForeignKey,
Model,
TextChoices,
TextField
)
class OperationType(TextChoices):
''' Type of operation. '''
INPUT = 'input'
SYNTHESIS = 'synthesis'
class Operation(Model):
''' Operational schema Unit.'''
oss: ForeignKey = ForeignKey(
verbose_name='Схема синтеза',
to='rsform.LibraryItem',
on_delete=CASCADE,
related_name='items'
)
operation_type: CharField = CharField(
verbose_name='Тип',
max_length=10,
choices=OperationType.choices,
default=OperationType.INPUT
)
result: ForeignKey = ForeignKey(
verbose_name='Связанная КС',
to='rsform.LibraryItem',
null=True,
on_delete=SET_NULL,
related_name='producer'
)
alias: CharField = CharField(
verbose_name='Шифр',
max_length=255,
blank=True
)
title: TextField = TextField(
verbose_name='Название',
blank=True
)
comment: TextField = TextField(
verbose_name='Комментарий',
blank=True
)
position_x: FloatField = FloatField(
verbose_name='Положение по горизонтали',
default=0
)
position_y: FloatField = FloatField(
verbose_name='Положение по вертикали',
default=0
)
class Meta:
''' Model metadata. '''
verbose_name = 'Операция'
verbose_name_plural = 'Операции'
def __str__(self) -> str:
return f'Операция {self.alias}'

View File

@ -0,0 +1,36 @@
''' Models: SynthesisSubstitution. '''
from django.db.models import CASCADE, BooleanField, ForeignKey, Model
class SynthesisSubstitution(Model):
''' Substitutions as part of Synthesis operation in OSS.'''
operation: ForeignKey = ForeignKey(
verbose_name='Операция',
to='oss.Operation',
on_delete=CASCADE
)
original: ForeignKey = ForeignKey(
verbose_name='Удаляемая конституента',
to='rsform.Constituenta',
on_delete=CASCADE,
related_name='as_original'
)
substitution: ForeignKey = ForeignKey(
verbose_name='Замещающая конституента',
to='rsform.Constituenta',
on_delete=CASCADE,
related_name='as_substitute'
)
transfer_term: BooleanField = BooleanField(
verbose_name='Перенос термина',
default=False
)
class Meta:
''' Model metadata. '''
verbose_name = 'Отождествление синтеза'
verbose_name_plural = 'Таблицы отождествлений'
def __str__(self) -> str:
return f'{self.original.pk} -> {self.substitution.pk}'

View File

@ -0,0 +1,8 @@
''' Django: Models. '''
from apps.rsform.models import LibraryItem, LibraryItemType
from .api_OSS import OperationSchema
from .Argument import Argument
from .Operation import Operation
from .SynthesisSubstitution import SynthesisSubstitution

View File

@ -0,0 +1,58 @@
''' Models: OSS API. '''
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import QuerySet
from apps.rsform.models import LibraryItem, LibraryItemType
from shared import messages as msg
from .Argument import Argument
from .Operation import Operation, OperationType
from .SynthesisSubstitution import SynthesisSubstitution
class OperationSchema:
''' Operations schema API. '''
def __init__(self, item: LibraryItem):
if item.item_type != LibraryItemType.OPERATION_SCHEMA:
raise ValueError(msg.libraryTypeUnexpected())
self.item = item
@staticmethod
def create(**kwargs) -> 'OperationSchema':
item = LibraryItem.objects.create(item_type=LibraryItemType.OPERATION_SCHEMA, **kwargs)
return OperationSchema(item=item)
def operations(self) -> QuerySet[Operation]:
''' Get QuerySet containing all operations of current OSS. '''
return Operation.objects.filter(oss=self.item)
def arguments(self) -> QuerySet[Argument]:
''' Operation arguments. '''
return Argument.objects.filter(operation__oss=self.item)
def substitutions(self) -> QuerySet[SynthesisSubstitution]:
''' Operation arguments. '''
return SynthesisSubstitution.objects.filter(operation__oss=self.item)
@transaction.atomic
def create_operation(self, operation_type: OperationType, alias: str, **kwargs) -> Operation:
''' Insert new operation. '''
if self.operations().filter(alias=alias).exists():
raise ValidationError(msg.aliasTaken(alias))
result = Operation.objects.create(
oss=self.item,
alias=alias,
operation_type=operation_type,
**kwargs
)
self.item.save()
result.refresh_from_db()
return result
@transaction.atomic
def delete_operation(self, operation: Operation):
''' Delete operation. '''
operation.delete()
self.item.save()

View File

@ -0,0 +1,11 @@
''' REST API: Serializers. '''
from apps.rsform.serializers import LibraryItemSerializer
from .data_access import (
ArgumentSerializer,
OperationCreateSerializer,
OperationSchemaSerializer,
OperationSerializer
)
from .schema_typing import NewOperationResponse

View File

@ -0,0 +1,97 @@
''' Serializers for persistent data manipulation. '''
from typing import cast
from rest_framework import serializers
from apps.rsform.models import LibraryItem
from apps.rsform.serializers import LibraryItemDetailsSerializer
from shared import messages as msg
from ..models import Argument, Operation, OperationSchema
class OperationSerializer(serializers.ModelSerializer):
''' Serializer: Operation data. '''
class Meta:
''' serializer metadata. '''
model = Operation
fields = '__all__'
read_only_fields = ('id', 'oss')
class ArgumentSerializer(serializers.ModelSerializer):
''' Serializer: Operation data. '''
class Meta:
''' serializer metadata. '''
model = Argument
fields = ('operation', 'argument')
class OperationPositionSerializer(serializers.ModelSerializer):
''' Serializer: Positions of a single operations in OSS. '''
class Meta:
''' serializer metadata. '''
model = Operation
fields = 'id', 'position_x', 'position_y'
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, self.instance)
if operation.oss != oss:
raise serializers.ValidationError({
'id': msg.operationNotOwned(oss.title)
})
return attrs
class OperationCreateSerializer(serializers.ModelSerializer):
''' Serializer: Constituenta creation. '''
positions = serializers.ListField(
child=OperationPositionSerializer(),
default=[]
)
class Meta:
''' serializer metadata. '''
model = Operation
fields = \
'alias', 'operation_type', 'title', \
'comment', 'position_x', 'position_y'
class OperationSchemaSerializer(serializers.ModelSerializer):
''' Serializer: Detailed data for OSS. '''
items = serializers.ListField(
child=OperationSerializer()
)
graph = serializers.ListField(
child=ArgumentSerializer()
)
class Meta:
''' serializer metadata. '''
model = LibraryItem
fields = '__all__'
def to_representation(self, instance: LibraryItem):
result = LibraryItemDetailsSerializer(instance).data
oss = OperationSchema(instance)
result['items'] = []
for operation in oss.operations():
result['items'].append(OperationSerializer(operation).data)
result['graph'] = []
for argument in oss.arguments():
result['graph'].append(ArgumentSerializer(argument).data)
result['substitutions'] = []
for substitution in oss.substitutions().values(
'original',
'original__alias',
'original__term_resolved',
'substitution',
'substitution__alias',
'substitution__term_resolved',
'transfer_term'
):
result['substitutions'].append(substitution)
return result

View File

@ -0,0 +1,10 @@
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
from rest_framework import serializers
from .data_access import OperationSchemaSerializer, OperationSerializer
class NewOperationResponse(serializers.Serializer):
''' Serializer: Create operation response. '''
new_operation = OperationSerializer()
oss = OperationSchemaSerializer()

View File

@ -0,0 +1,12 @@
''' Routing: Operation Schema. '''
from django.urls import include, path
from rest_framework import routers
from . import views
library_router = routers.SimpleRouter(trailing_slash=False)
library_router.register('rsforms', views.OssViewSet, 'RSForm')
urlpatterns = [
path('', include(library_router.urls)),
]

View File

@ -0,0 +1,2 @@
''' REST API: Endpoint processors. '''
from .oss import OssViewSet

View File

@ -0,0 +1,110 @@
''' Endpoints for OSS. '''
from typing import cast
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import generics
from rest_framework import status as c
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from shared import permissions
from .. import models as m
from .. import serializers as s
@extend_schema(tags=['OSS'])
@extend_schema_view()
class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
''' Endpoint: OperationSchema. '''
queryset = m.LibraryItem.objects.filter(item_type=m.LibraryItemType.OPERATION_SCHEMA)
serializer_class = s.LibraryItemSerializer
def _get_schema(self) -> m.OperationSchema:
return m.OperationSchema(cast(m.LibraryItem, self.get_object()))
def get_permissions(self):
''' Determine permission class. '''
if self.action in [
'operation_create',
'operation_delete'
]:
permission_list = [permissions.ItemEditor]
elif self.action in ['details']:
permission_list = [permissions.ItemAnyone]
else:
permission_list = [permissions.Anyone]
return [permission() for permission in permission_list]
@extend_schema(
summary='get operations data',
tags=['OSS'],
request=None,
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['get'], url_path='details')
def details(self, request: Request, pk):
''' Endpoint: Detailed OSS data. '''
serializer = s.OperationSchemaSerializer(cast(m.LibraryItem, self.get_object()))
return Response(
status=c.HTTP_200_OK,
data=serializer.data
)
@extend_schema(
summary='create operation',
tags=['OSS'],
request=s.OperationCreateSerializer(),
responses={
c.HTTP_201_CREATED: s.NewOperationResponse,
c.HTTP_403_FORBIDDEN: None
}
)
@action(detail=True, methods=['post'], url_path='operation-create')
def operation_create(self, request: Request, pk):
''' Create new operation. '''
schema = self._get_schema()
serializer = s.OperationCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
new_operation = schema.create_operation(*data)
schema.item.refresh_from_db()
response = Response(
status=c.HTTP_201_CREATED,
data={
'new_operation': s.OperationSerializer(new_operation).data,
'schema': s.OperationSchemaSerializer(schema.item).data
}
)
return response
# @extend_schema(
# summary='delete operation',
# tags=['RSForm'],
# request=s.CstListSerializer,
# responses={
# c.HTTP_200_OK: s.RSFormParseSerializer,
# c.HTTP_403_FORBIDDEN: None,
# c.HTTP_404_NOT_FOUND: None
# }
# )
# @action(detail=True, methods=['patch'], url_path='operation-delete')
# def operation_delete(self, request: Request, pk):
# ''' Endpoint: Delete operation. '''
# schema = self._get_schema()
# serializer = s.CstListSerializer(
# data=request.data,
# context={'schema': schema.item}
# )
# serializer.is_valid(raise_exception=True)
# schema.delete_cst(serializer.validated_data['items'])
# schema.item.refresh_from_db()
# return Response(
# status=c.HTTP_200_OK,
# data=s.RSFormParseSerializer(schema.item).data
# )

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-07-17 09:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rsform', '0007_location_and_flags'),
]
operations = [
migrations.AlterField(
model_name='libraryitem',
name='item_type',
field=models.CharField(choices=[('rsform', 'Rsform'), ('oss', 'Operation Schema')], max_length=50, verbose_name='Тип'),
),
]

View File

@ -9,6 +9,7 @@ from django.db.models import (
DateTimeField,
ForeignKey,
Model,
QuerySet,
TextChoices,
TextField
)
@ -113,18 +114,18 @@ class LibraryItem(Model):
def get_absolute_url(self):
return f'/api/library/{self.pk}'
def subscribers(self) -> list[Subscription]:
def subscribers(self) -> list[User]:
''' Get all subscribers for this item. '''
return [subscription.user for subscription in Subscription.objects.filter(item=self.pk).only('user')]
def versions(self) -> list[Version]:
''' Get all Versions of this item. '''
return list(Version.objects.filter(item=self.pk).order_by('-time_create'))
def editors(self) -> list[Editor]:
def editors(self) -> list[User]:
''' Get all Editors of this item. '''
return [item.editor for item in Editor.objects.filter(item=self.pk).only('editor')]
def versions(self) -> QuerySet[Version]:
''' Get all Versions of this item. '''
return Version.objects.filter(item=self.pk).order_by('-time_create')
@transaction.atomic
def save(self, *args, **kwargs):
subscribe = not self.pk and self.owner

View File

@ -7,7 +7,8 @@ from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import QuerySet
from .. import messages as msg
from shared import messages as msg
from ..graph import Graph
from .api_RSLanguage import (
extract_globals,

View File

@ -6,7 +6,8 @@ from typing import Set, Tuple, cast
import pyconcept
from .. import messages as msg
from shared import messages as msg
from .Constituenta import CstType
_RE_GLOBALS = r'[XCSADFPT]\d+' # cspell:disable-line

View File

@ -22,6 +22,7 @@ from .data_access import (
InlineSynthesisSerializer,
LibraryItemBaseSerializer,
LibraryItemCloneSerializer,
LibraryItemDetailsSerializer,
LibraryItemSerializer,
RSFormParseSerializer,
RSFormSerializer,

View File

@ -4,7 +4,8 @@ from typing import cast
from cctext import EntityReference, Reference, ReferenceType, Resolver, SyntacticReference
from rest_framework import serializers
from .. import messages as msg
from shared import messages as msg
from ..models import AccessPolicy, validate_location

View File

@ -7,7 +7,8 @@ from django.db import transaction
from rest_framework import serializers
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
from .. import messages as msg
from shared import messages as msg
from ..models import Constituenta, CstType, LibraryItem, RSForm, Version
from .basics import CstParseSerializer
from .io_pyconcept import PyConceptAdapter

View File

@ -2,7 +2,8 @@
from django.db import transaction
from rest_framework import serializers
from .. import messages as msg
from shared import messages as msg
from ..models import Constituenta, LibraryItem, RSForm
from ..utils import fix_old_references

View File

@ -4,7 +4,8 @@ from typing import Optional, Union, cast
import pyconcept
from .. import messages as msg
from shared import messages as msg
from ..models import RSForm

View File

@ -1,7 +1,7 @@
''' Testing views '''
from cctext import split_grams
from ..EndpointTester import EndpointTester, decl_endpoint
from shared.EndpointTester import EndpointTester, decl_endpoint
class TestNaturalLanguageViews(EndpointTester):

View File

@ -1,7 +1,6 @@
''' Testing API: Constituents. '''
from apps.rsform.models import Constituenta, CstType, RSForm
from ..EndpointTester import EndpointTester, decl_endpoint
from shared.EndpointTester import EndpointTester, decl_endpoint
class TestConstituentaAPI(EndpointTester):

View File

@ -11,9 +11,8 @@ from apps.rsform.models import (
RSForm,
Subscription
)
from ..EndpointTester import EndpointTester, decl_endpoint
from ..testing_utils import response_contains
from shared.EndpointTester import EndpointTester, decl_endpoint
from shared.testing_utils import response_contains
class TestLibraryViewset(EndpointTester):

View File

@ -1,7 +1,6 @@
''' Testing API: Operations. '''
from apps.rsform.models import Constituenta, CstType, RSForm
from ..EndpointTester import EndpointTester, decl_endpoint
from shared.EndpointTester import EndpointTester, decl_endpoint
class TestInlineSynthesis(EndpointTester):

View File

@ -15,9 +15,8 @@ from apps.rsform.models import (
LocationHead,
RSForm
)
from ..EndpointTester import EndpointTester, decl_endpoint
from ..testing_utils import response_contains
from shared.EndpointTester import EndpointTester, decl_endpoint
from shared.testing_utils import response_contains
class TestRSFormViewset(EndpointTester):

View File

@ -1,5 +1,5 @@
''' Testing views '''
from ..EndpointTester import EndpointTester, decl_endpoint
from shared.EndpointTester import EndpointTester, decl_endpoint
class TestRSLanguageViews(EndpointTester):

View File

@ -7,8 +7,7 @@ from zipfile import ZipFile
from rest_framework import status
from apps.rsform.models import Constituenta, RSForm
from ..EndpointTester import EndpointTester, decl_endpoint
from shared.EndpointTester import EndpointTester, decl_endpoint
class TestVersionViews(EndpointTester):

View File

@ -2,8 +2,9 @@
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import generics
from shared import permissions
from .. import models as m
from .. import permissions
from .. import serializers as s

View File

@ -13,8 +13,9 @@ from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from shared import permissions
from .. import models as m
from .. import permissions
from .. import serializers as s

View File

@ -13,9 +13,10 @@ from rest_framework.decorators import action, api_view
from rest_framework.request import Request
from rest_framework.response import Response
from .. import messages as msg
from shared import messages as msg
from shared import permissions
from .. import models as m
from .. import permissions
from .. import serializers as s
from .. import utils

View File

@ -10,8 +10,9 @@ from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.request import Request
from rest_framework.response import Response
from shared import permissions
from .. import models as m
from .. import permissions
from .. import serializers as s
from .. import utils

View File

@ -1,14 +0,0 @@
''' Utility: Text messages. '''
# pylint: skip-file
def passwordAuthFailed():
return 'Неизвестное сочетание имени пользователя (email) и пароля'
def passwordsNotMatch():
return 'Введенные пароли не совпадают'
def emailAlreadyTaken():
return 'Пользователь с данным email уже существует'

View File

@ -4,8 +4,8 @@ from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers
from apps.rsform.models import Editor, Subscription
from shared import messages as msg
from . import messages as msg
from . import models

View File

@ -1,8 +1,8 @@
''' Testing API: users. '''
from rest_framework.test import APIClient, APITestCase
from apps.rsform.tests.EndpointTester import EndpointTester, decl_endpoint
from apps.users.models import User
from shared.EndpointTester import EndpointTester, decl_endpoint
class TestUserAPIViews(EndpointTester):

View File

@ -74,6 +74,7 @@ INSTALLED_APPS = [
'apps.users',
'apps.rsform',
'apps.oss',
'drf_spectacular',
'drf_spectacular_sidecar',

View File

@ -0,0 +1 @@
''' Utilities shared between applications. '''

View File

@ -6,6 +6,10 @@ def constituentaNotOwned(title: str):
return f'Конституента не принадлежит схеме: {title}'
def operationNotOwned(title: str):
return f'Операция не принадлежит схеме: {title}'
def substitutionNotInList():
return 'Отождествляемая конституента отсутствует в списке'
@ -64,3 +68,15 @@ def constituentaNoStructure():
def missingFile():
return 'Отсутствует прикрепленный файл'
def passwordAuthFailed():
return 'Неизвестное сочетание имени пользователя (email) и пароля'
def passwordsNotMatch():
return 'Введенные пароли не совпадают'
def emailAlreadyTaken():
return 'Пользователь с данным email уже существует'

View File

@ -11,16 +11,24 @@ from rest_framework.permissions import \
from rest_framework.request import Request
from rest_framework.views import APIView
from . import models as m
from apps.rsform.models import (
AccessPolicy,
Constituenta,
Editor,
LibraryItem,
Subscription,
Version
)
from apps.users.models import User
def _extract_item(obj: Any) -> m.LibraryItem:
if isinstance(obj, m.LibraryItem):
def _extract_item(obj: Any) -> LibraryItem:
if isinstance(obj, LibraryItem):
return obj
elif isinstance(obj, m.Constituenta):
return cast(m.LibraryItem, obj.schema)
elif isinstance(obj, (m.Version, m.Subscription, m.Editor)):
return cast(m.LibraryItem, obj.item)
elif isinstance(obj, Constituenta):
return cast(LibraryItem, obj.schema)
elif isinstance(obj, (Version, Subscription, Editor)):
return cast(LibraryItem, obj.item)
raise PermissionDenied({
'message': 'Invalid type error. Please contact developers',
'object_id': obj.id
@ -60,10 +68,10 @@ class ItemEditor(ItemOwner):
if request.user.is_anonymous:
return False
item = _extract_item(obj)
if m.Editor.objects.filter(
if Editor.objects.filter(
item=item,
editor=cast(m.User, request.user)
).exists() and item.access_policy != m.AccessPolicy.PRIVATE:
editor=cast(User, request.user)
).exists() and item.access_policy != AccessPolicy.PRIVATE:
return True
return super().has_object_permission(request, view, obj)
@ -76,7 +84,7 @@ class ItemAnyone(ItemEditor):
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool:
item = _extract_item(obj)
if item.access_policy == m.AccessPolicy.PUBLIC:
if item.access_policy == AccessPolicy.PUBLIC:
return True
return super().has_object_permission(request, view, obj)