diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 4ac7da68..1478643f 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -6,16 +6,15 @@ defaults: on: push: - branches: [ "main" ] + branches: ["main"] paths: - rsconcept/backend/** - .github/workflows/backend.yml pull_request: - branches: [ "main" ] + branches: ["main"] jobs: build: - runs-on: ubuntu-22.04 strategy: max-parallel: 4 @@ -23,21 +22,21 @@ jobs: python-version: [3.12] steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements-dev.txt - - name: Lint - run: | - pylint project apps - mypy project apps - - name: Run Tests - if: '!cancelled()' - run: | - python manage.py check - python manage.py test + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev-lock.txt + - name: Lint + run: | + pylint project apps + mypy project apps + - name: Run Tests + if: "!cancelled()" + run: | + python manage.py check + python manage.py test diff --git a/rsconcept/backend/apps/library/admin.py b/rsconcept/backend/apps/library/admin.py index 24111083..35d34ab7 100644 --- a/rsconcept/backend/apps/library/admin.py +++ b/rsconcept/backend/apps/library/admin.py @@ -1,4 +1,5 @@ ''' Admin view: Library. ''' +from typing import cast from django.contrib import admin from . import models @@ -23,7 +24,7 @@ class LibraryTemplateAdmin(admin.ModelAdmin): def alias(self, template: models.LibraryTemplate): if template.lib_source: - return template.lib_source.alias + return cast(models.LibraryItem, template.lib_source).alias else: return 'N/A' diff --git a/rsconcept/backend/apps/library/migrations/0005_alter_libraryitem_owner.py b/rsconcept/backend/apps/library/migrations/0005_alter_libraryitem_owner.py new file mode 100644 index 00000000..4d633179 --- /dev/null +++ b/rsconcept/backend/apps/library/migrations/0005_alter_libraryitem_owner.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.1 on 2024-09-12 16:48 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('library', '0004_delete_subscription'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='libraryitem', + name='owner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Владелец'), + ), + ] diff --git a/rsconcept/backend/apps/library/models/Editor.py b/rsconcept/backend/apps/library/models/Editor.py index 7c853815..14a8dab5 100644 --- a/rsconcept/backend/apps/library/models/Editor.py +++ b/rsconcept/backend/apps/library/models/Editor.py @@ -9,17 +9,17 @@ from apps.users.models import User class Editor(Model): ''' Editor list. ''' - item: ForeignKey = ForeignKey( + item = ForeignKey( verbose_name='Схема', to='library.LibraryItem', on_delete=CASCADE ) - editor: ForeignKey = ForeignKey( + editor = ForeignKey( verbose_name='Редактор', to=User, on_delete=CASCADE ) - time_create: DateTimeField = DateTimeField( + time_create = DateTimeField( verbose_name='Дата добавления', auto_now_add=True ) diff --git a/rsconcept/backend/apps/library/models/LibraryItem.py b/rsconcept/backend/apps/library/models/LibraryItem.py index 9ba22a56..bf0f6082 100644 --- a/rsconcept/backend/apps/library/models/LibraryItem.py +++ b/rsconcept/backend/apps/library/models/LibraryItem.py @@ -48,55 +48,56 @@ def validate_location(target: str) -> bool: class LibraryItem(Model): ''' Abstract library item.''' - item_type: CharField = CharField( + item_type = CharField( verbose_name='Тип', max_length=50, choices=LibraryItemType.choices, default=LibraryItemType.RSFORM ) - owner: ForeignKey = ForeignKey( + owner = ForeignKey( verbose_name='Владелец', to=User, on_delete=SET_NULL, + blank=True, null=True ) - title: TextField = TextField( + title = TextField( verbose_name='Название' ) - alias: CharField = CharField( + alias = CharField( verbose_name='Шифр', max_length=255, blank=True ) - comment: TextField = TextField( + comment = TextField( verbose_name='Комментарий', blank=True ) - visible: BooleanField = BooleanField( + visible = BooleanField( verbose_name='Отображаемая', default=True ) - read_only: BooleanField = BooleanField( + read_only = BooleanField( verbose_name='Запретить редактирование', default=False ) - access_policy: CharField = CharField( + access_policy = CharField( verbose_name='Политика доступа', max_length=500, choices=AccessPolicy.choices, default=AccessPolicy.PUBLIC ) - location: TextField = TextField( + location = TextField( verbose_name='Расположение', max_length=500, default=LocationHead.USER ) - time_create: DateTimeField = DateTimeField( + time_create = DateTimeField( verbose_name='Дата создания', auto_now_add=True ) - time_update: DateTimeField = DateTimeField( + time_update = DateTimeField( verbose_name='Дата изменения', auto_now=True ) @@ -112,11 +113,11 @@ class LibraryItem(Model): def get_absolute_url(self): return f'/api/library/{self.pk}' - def editors(self) -> QuerySet[User]: + def getQ_editors(self) -> QuerySet[User]: ''' Get all Editors of this item. ''' return User.objects.filter(editor__item=self.pk) - def versions(self) -> QuerySet[Version]: + def getQ_versions(self) -> QuerySet[Version]: ''' Get all Versions of this item. ''' return Version.objects.filter(item=self.pk).order_by('-time_create') diff --git a/rsconcept/backend/apps/library/models/LibraryTemplate.py b/rsconcept/backend/apps/library/models/LibraryTemplate.py index ca0f2307..07b54374 100644 --- a/rsconcept/backend/apps/library/models/LibraryTemplate.py +++ b/rsconcept/backend/apps/library/models/LibraryTemplate.py @@ -4,7 +4,7 @@ from django.db.models import CASCADE, ForeignKey, Model class LibraryTemplate(Model): ''' Template for library items and constituents. ''' - lib_source: ForeignKey = ForeignKey( + lib_source = ForeignKey( verbose_name='Источник', to='library.LibraryItem', on_delete=CASCADE diff --git a/rsconcept/backend/apps/library/models/Version.py b/rsconcept/backend/apps/library/models/Version.py index c12f3315..ae3879c9 100644 --- a/rsconcept/backend/apps/library/models/Version.py +++ b/rsconcept/backend/apps/library/models/Version.py @@ -12,7 +12,7 @@ from django.db.models import ( class Version(Model): ''' Library item version archive. ''' - item: ForeignKey = ForeignKey( + item = ForeignKey( verbose_name='Схема', to='library.LibraryItem', on_delete=CASCADE @@ -22,14 +22,14 @@ class Version(Model): max_length=20, blank=False ) - description: TextField = TextField( + description = TextField( verbose_name='Описание', blank=True ) - data: JSONField = JSONField( + data = JSONField( verbose_name='Содержание' ) - time_create: DateTimeField = DateTimeField( + time_create = DateTimeField( verbose_name='Дата создания', auto_now_add=True ) diff --git a/rsconcept/backend/apps/library/serializers/data_access.py b/rsconcept/backend/apps/library/serializers/data_access.py index b010b09a..726c9d7e 100644 --- a/rsconcept/backend/apps/library/serializers/data_access.py +++ b/rsconcept/backend/apps/library/serializers/data_access.py @@ -84,10 +84,10 @@ class LibraryItemDetailsSerializer(serializers.ModelSerializer): read_only_fields = ('owner', 'id', 'item_type') def get_editors(self, instance: LibraryItem) -> list[int]: - return list(instance.editors().order_by('pk').values_list('pk', flat=True)) + return list(instance.getQ_editors().order_by('pk').values_list('pk', flat=True)) def get_versions(self, instance: LibraryItem) -> list: - return [VersionInnerSerializer(item).data for item in instance.versions().order_by('pk')] + return [VersionInnerSerializer(item).data for item in instance.getQ_versions().order_by('pk')] class UserTargetSerializer(serializers.Serializer): diff --git a/rsconcept/backend/apps/library/tests/s_models/t_Editor.py b/rsconcept/backend/apps/library/tests/s_models/t_Editor.py index 779ed9f2..a54b53bf 100644 --- a/rsconcept/backend/apps/library/tests/s_models/t_Editor.py +++ b/rsconcept/backend/apps/library/tests/s_models/t_Editor.py @@ -35,64 +35,64 @@ class TestEditor(TestCase): def test_add_editor(self): self.assertTrue(Editor.add(self.item.pk, self.user1.pk)) - self.assertEqual(self.item.editors().count(), 1) - self.assertTrue(self.user1 in list(self.item.editors())) + self.assertEqual(self.item.getQ_editors().count(), 1) + self.assertTrue(self.user1 in list(self.item.getQ_editors())) self.assertFalse(Editor.add(self.item.pk, self.user1.pk)) - self.assertEqual(self.item.editors().count(), 1) + self.assertEqual(self.item.getQ_editors().count(), 1) self.assertTrue(Editor.add(self.item.pk, self.user2.pk)) - self.assertEqual(self.item.editors().count(), 2) - self.assertTrue(self.user1 in self.item.editors()) - self.assertTrue(self.user2 in self.item.editors()) + self.assertEqual(self.item.getQ_editors().count(), 2) + self.assertTrue(self.user1 in self.item.getQ_editors()) + self.assertTrue(self.user2 in self.item.getQ_editors()) self.user1.delete() - self.assertEqual(self.item.editors().count(), 1) + self.assertEqual(self.item.getQ_editors().count(), 1) def test_remove_editor(self): self.assertFalse(Editor.remove(self.item.pk, self.user1.pk)) Editor.add(self.item.pk, self.user1.pk) Editor.add(self.item.pk, self.user2.pk) - self.assertEqual(self.item.editors().count(), 2) + self.assertEqual(self.item.getQ_editors().count(), 2) self.assertTrue(Editor.remove(self.item.pk, self.user1.pk)) - self.assertEqual(self.item.editors().count(), 1) - self.assertTrue(self.user2 in self.item.editors()) + self.assertEqual(self.item.getQ_editors().count(), 1) + self.assertTrue(self.user2 in self.item.getQ_editors()) self.assertFalse(Editor.remove(self.item.pk, self.user1.pk)) def test_set_editors(self): Editor.set(self.item.pk, [self.user1.pk]) - self.assertEqual(list(self.item.editors()), [self.user1]) + self.assertEqual(list(self.item.getQ_editors()), [self.user1]) Editor.set(self.item.pk, [self.user1.pk, self.user1.pk]) - self.assertEqual(list(self.item.editors()), [self.user1]) + self.assertEqual(list(self.item.getQ_editors()), [self.user1]) Editor.set(self.item.pk, []) - self.assertEqual(list(self.item.editors()), []) + self.assertEqual(list(self.item.getQ_editors()), []) Editor.set(self.item.pk, [self.user1.pk, self.user2.pk]) - self.assertEqual(set(self.item.editors()), set([self.user1, self.user2])) + self.assertEqual(set(self.item.getQ_editors()), set([self.user1, self.user2])) def test_set_editors_return_diff(self): added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk]) self.assertEqual(added, [self.user1.pk]) self.assertEqual(deleted, []) - self.assertEqual(list(self.item.editors()), [self.user1]) + self.assertEqual(list(self.item.getQ_editors()), [self.user1]) added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk, self.user1.pk]) self.assertEqual(added, []) self.assertEqual(deleted, []) - self.assertEqual(list(self.item.editors()), [self.user1]) + self.assertEqual(list(self.item.getQ_editors()), [self.user1]) added, deleted = Editor.set_and_return_diff(self.item.pk, []) self.assertEqual(added, []) self.assertEqual(deleted, [self.user1.pk]) - self.assertEqual(list(self.item.editors()), []) + self.assertEqual(list(self.item.getQ_editors()), []) added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk, self.user2.pk]) self.assertEqual(added, [self.user1.pk, self.user2.pk]) self.assertEqual(deleted, []) - self.assertEqual(set(self.item.editors()), set([self.user1, self.user2])) + self.assertEqual(set(self.item.getQ_editors()), set([self.user1, self.user2])) diff --git a/rsconcept/backend/apps/library/tests/s_views/t_library.py b/rsconcept/backend/apps/library/tests/s_views/t_library.py index 54863027..32403005 100644 --- a/rsconcept/backend/apps/library/tests/s_views/t_library.py +++ b/rsconcept/backend/apps/library/tests/s_views/t_library.py @@ -250,22 +250,22 @@ class TestLibraryViewset(EndpointTester): self.executeOK(data=data, item=self.owned.pk) self.owned.refresh_from_db() self.assertEqual(self.owned.time_update, time_update) - self.assertEqual(list(self.owned.editors()), [self.user]) + self.assertEqual(list(self.owned.getQ_editors()), [self.user]) self.executeOK(data=data) - self.assertEqual(list(self.owned.editors()), [self.user]) + self.assertEqual(list(self.owned.getQ_editors()), [self.user]) data = {'users': [self.user2.pk]} self.executeOK(data=data) - self.assertEqual(list(self.owned.editors()), [self.user2]) + self.assertEqual(list(self.owned.getQ_editors()), [self.user2]) data = {'users': []} self.executeOK(data=data) - self.assertEqual(list(self.owned.editors()), []) + self.assertEqual(list(self.owned.getQ_editors()), []) data = {'users': [self.user2.pk, self.user.pk]} self.executeOK(data=data) - self.assertEqual(set(self.owned.editors()), set([self.user2, self.user])) + self.assertEqual(set(self.owned.getQ_editors()), set([self.user2, self.user])) @decl_endpoint('/api/library/{item}', method='delete') diff --git a/rsconcept/backend/apps/library/views/library.py b/rsconcept/backend/apps/library/views/library.py index 129d1ecb..e58d789f 100644 --- a/rsconcept/backend/apps/library/views/library.py +++ b/rsconcept/backend/apps/library/views/library.py @@ -157,7 +157,7 @@ class LibraryViewSet(viewsets.ModelViewSet): clone = deepcopy(item) clone.pk = None - clone.owner = self.request.user + clone.owner = cast(User, self.request.user) clone.title = serializer.validated_data['title'] clone.alias = serializer.validated_data.get('alias', '') clone.comment = serializer.validated_data.get('comment', '') diff --git a/rsconcept/backend/apps/library/views/versions.py b/rsconcept/backend/apps/library/views/versions.py index 813d1692..1a0a67c5 100644 --- a/rsconcept/backend/apps/library/views/versions.py +++ b/rsconcept/backend/apps/library/views/versions.py @@ -44,7 +44,7 @@ class VersionViewset( def restore(self, request: Request, pk) -> HttpResponse: ''' Restore version data into current item. ''' version = cast(m.Version, self.get_object()) - item = cast(m.LibraryItem, version.item) + item = version.item with transaction.atomic(): RSFormSerializer(item).restore_from_version(version.data) return Response( diff --git a/rsconcept/backend/apps/oss/migrations/0008_alter_operation_result.py b/rsconcept/backend/apps/oss/migrations/0008_alter_operation_result.py new file mode 100644 index 00000000..93d81de4 --- /dev/null +++ b/rsconcept/backend/apps/oss/migrations/0008_alter_operation_result.py @@ -0,0 +1,20 @@ +# Generated by Django 5.1.1 on 2024-09-12 16:48 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('library', '0005_alter_libraryitem_owner'), + ('oss', '0007_argument_order'), + ] + + operations = [ + migrations.AlterField( + model_name='operation', + name='result', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='producer', to='library.libraryitem', verbose_name='Связанная КС'), + ), + ] diff --git a/rsconcept/backend/apps/oss/models/Argument.py b/rsconcept/backend/apps/oss/models/Argument.py index 14bd27b7..baa92f7d 100644 --- a/rsconcept/backend/apps/oss/models/Argument.py +++ b/rsconcept/backend/apps/oss/models/Argument.py @@ -4,19 +4,19 @@ from django.db.models import CASCADE, ForeignKey, Model, PositiveIntegerField class Argument(Model): ''' Operation Argument.''' - operation: ForeignKey = ForeignKey( + operation = ForeignKey( verbose_name='Операция', to='oss.Operation', on_delete=CASCADE, related_name='arguments' ) - argument: ForeignKey = ForeignKey( + argument = ForeignKey( verbose_name='Аргумент', to='oss.Operation', on_delete=CASCADE, related_name='descendants' ) - order: PositiveIntegerField = PositiveIntegerField( + order = PositiveIntegerField( verbose_name='Позиция', default=0, ) diff --git a/rsconcept/backend/apps/oss/models/Inheritance.py b/rsconcept/backend/apps/oss/models/Inheritance.py index f6d3c4f8..761fe58f 100644 --- a/rsconcept/backend/apps/oss/models/Inheritance.py +++ b/rsconcept/backend/apps/oss/models/Inheritance.py @@ -4,19 +4,19 @@ from django.db.models import CASCADE, ForeignKey, Model class Inheritance(Model): ''' Inheritance links parent and child constituents in synthesis operation.''' - operation: ForeignKey = ForeignKey( + operation = ForeignKey( verbose_name='Операция', to='oss.Operation', on_delete=CASCADE, related_name='inheritances' ) - parent: ForeignKey = ForeignKey( + parent = ForeignKey( verbose_name='Исходная конституента', to='rsform.Constituenta', on_delete=CASCADE, related_name='as_parent' ) - child: ForeignKey = ForeignKey( + child = ForeignKey( verbose_name='Наследованная конституента', to='rsform.Constituenta', on_delete=CASCADE, diff --git a/rsconcept/backend/apps/oss/models/Operation.py b/rsconcept/backend/apps/oss/models/Operation.py index 021f50ff..b7562879 100644 --- a/rsconcept/backend/apps/oss/models/Operation.py +++ b/rsconcept/backend/apps/oss/models/Operation.py @@ -23,45 +23,46 @@ class OperationType(TextChoices): class Operation(Model): ''' Operational schema Unit.''' - oss: ForeignKey = ForeignKey( + oss = ForeignKey( verbose_name='Схема синтеза', to='library.LibraryItem', on_delete=CASCADE, related_name='operations' ) - operation_type: CharField = CharField( + operation_type = CharField( verbose_name='Тип', max_length=10, choices=OperationType.choices, default=OperationType.INPUT ) - result: ForeignKey = ForeignKey( + result = ForeignKey( verbose_name='Связанная КС', to='library.LibraryItem', + blank=True, null=True, on_delete=SET_NULL, related_name='producer' ) - alias: CharField = CharField( + alias = CharField( verbose_name='Шифр', max_length=255, blank=True ) - title: TextField = TextField( + title = TextField( verbose_name='Название', blank=True ) - comment: TextField = TextField( + comment = TextField( verbose_name='Комментарий', blank=True ) - position_x: FloatField = FloatField( + position_x = FloatField( verbose_name='Положение по горизонтали', default=0 ) - position_y: FloatField = FloatField( + position_y = FloatField( verbose_name='Положение по вертикали', default=0 ) @@ -74,10 +75,10 @@ class Operation(Model): def __str__(self) -> str: return f'Операция {self.alias}' - def getArguments(self) -> QuerySet[Argument]: + def getQ_arguments(self) -> QuerySet[Argument]: ''' Operation arguments. ''' return Argument.objects.filter(operation=self) - def getSubstitutions(self) -> QuerySet[Substitution]: + def getQ_substitutions(self) -> QuerySet[Substitution]: ''' Operation substitutions. ''' return Substitution.objects.filter(operation=self) diff --git a/rsconcept/backend/apps/oss/models/OperationSchema.py b/rsconcept/backend/apps/oss/models/OperationSchema.py index 16195ad3..6ab9fae6 100644 --- a/rsconcept/backend/apps/oss/models/OperationSchema.py +++ b/rsconcept/backend/apps/oss/models/OperationSchema.py @@ -121,7 +121,6 @@ class OperationSchema: operation.result = schema if schema is not None: - operation.result = schema operation.alias = schema.alias operation.title = schema.title operation.comment = schema.comment @@ -139,7 +138,7 @@ class OperationSchema: processed: list[Operation] = [] updated: list[Argument] = [] deleted: list[Argument] = [] - for current in operation.getArguments(): + for current in operation.getQ_arguments(): if current.argument not in arguments: deleted.append(current) else: @@ -172,7 +171,7 @@ class OperationSchema: schema = self.cache.get_schema(operation) processed: list[dict] = [] deleted: list[Substitution] = [] - for current in operation.getSubstitutions(): + for current in operation.getQ_substitutions(): subs = [ x for x in substitutes if x['original'] == current.original and x['substitution'] == current.substitution @@ -215,7 +214,7 @@ class OperationSchema: access_policy=self.model.access_policy, location=self.model.location ) - Editor.set(schema.model.pk, self.model.editors().values_list('pk', flat=True)) + Editor.set(schema.model.pk, self.model.getQ_editors().values_list('pk', flat=True)) operation.result = schema.model operation.save() self.save(update_fields=['time_update']) @@ -223,10 +222,14 @@ class OperationSchema: def execute_operation(self, operation: Operation) -> bool: ''' Execute target operation. ''' - schemas: list[LibraryItem] = [arg.argument.result for arg in operation.getArguments().order_by('order')] - if None in schemas: + schemas = [ + arg.argument.result + for arg in operation.getQ_arguments().order_by('order') + if arg.argument.result is not None + ] + if len(schemas) == 0: return False - substitutions = operation.getSubstitutions() + substitutions = operation.getQ_substitutions() receiver = self.create_input(self.cache.operation_by_id[operation.pk]) parents: dict = {} @@ -284,7 +287,7 @@ class OperationSchema: ''' Trigger cascade resolutions when constituenta type is changed. ''' self.cache.insert_schema(source) operation = self.cache.get_operation(source.model.pk) - self._cascade_change_cst_type(operation.pk, target.pk, target.cst_type) + self._cascade_change_cst_type(operation.pk, target.pk, cast(CstType, target.cst_type)) def after_update_cst(self, source: RSForm, target: Constituenta, data: dict, old_data: dict) -> None: ''' Trigger cascade resolutions when constituenta data is changed. ''' @@ -659,7 +662,7 @@ class OperationSchema: substitution_id = self.cache.get_inheritor(substitution_cst.pk, operation_id) assert substitution_id is not None substitution_inheritor = schema.cache.by_id[substitution_id] - mapping = {cast(str, substitution_inheritor.alias): new_original} + mapping = {substitution_inheritor.alias: new_original} self._cascade_partial_mapping(mapping, dependant, operation_id, schema) def _process_added_substitutions(self, schema: Optional[RSForm], added: list[Substitution]) -> None: diff --git a/rsconcept/backend/apps/oss/models/Substitution.py b/rsconcept/backend/apps/oss/models/Substitution.py index e5e39abd..84c54983 100644 --- a/rsconcept/backend/apps/oss/models/Substitution.py +++ b/rsconcept/backend/apps/oss/models/Substitution.py @@ -4,19 +4,19 @@ from django.db.models import CASCADE, ForeignKey, Model class Substitution(Model): ''' Substitutions as part of Synthesis operation in OSS.''' - operation: ForeignKey = ForeignKey( + operation = ForeignKey( verbose_name='Операция', to='oss.Operation', on_delete=CASCADE ) - original: ForeignKey = ForeignKey( + original = ForeignKey( verbose_name='Удаляемая конституента', to='rsform.Constituenta', on_delete=CASCADE, related_name='as_original' ) - substitution: ForeignKey = ForeignKey( + substitution = ForeignKey( verbose_name='Замещающая конституента', to='rsform.Constituenta', on_delete=CASCADE, diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py index 36edb98e..c0e81a09 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py @@ -53,6 +53,7 @@ class TestChangeAttributes(EndpointTester): alias='3', operation_type=OperationType.SYNTHESIS ) + self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2]) self.owned.execute_operation(self.operation3) self.operation3.refresh_from_db() self.ks3 = RSForm(self.operation3.result) @@ -116,10 +117,10 @@ class TestChangeAttributes(EndpointTester): self.ks1.refresh_from_db() self.ks2.refresh_from_db() self.ks3.refresh_from_db() - self.assertEqual(list(self.owned.model.editors()), [self.user3]) - self.assertEqual(list(self.ks1.model.editors()), [self.user, self.user2]) - self.assertEqual(list(self.ks2.model.editors()), []) - self.assertEqual(set(self.ks3.model.editors()), set([self.user, self.user3])) + self.assertEqual(list(self.owned.model.getQ_editors()), [self.user3]) + self.assertEqual(list(self.ks1.model.getQ_editors()), [self.user, self.user2]) + self.assertEqual(list(self.ks2.model.getQ_editors()), []) + self.assertEqual(set(self.ks3.model.getQ_editors()), set([self.user, self.user3])) @decl_endpoint('/api/library/{item}', method='patch') def test_sync_from_result(self): diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py index 4492066b..ab1bf053 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py @@ -124,9 +124,9 @@ class TestChangeOperations(EndpointTester): self.ks4D1.refresh_from_db() self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks4.constituents().count(), 4) self.assertEqual(self.ks5.constituents().count(), 6) @@ -147,9 +147,9 @@ class TestChangeOperations(EndpointTester): self.ks5D4.refresh_from_db() self.operation2.refresh_from_db() self.assertEqual(self.operation2.result, None) - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks4.constituents().count(), 4) self.assertEqual(self.ks5.constituents().count(), 6) @@ -181,9 +181,9 @@ class TestChangeOperations(EndpointTester): self.operation2.refresh_from_db() self.assertEqual(self.operation2.result, ks6.model) self.assertEqual(self.operation2.alias, ks6.model.alias) - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks4.constituents().count(), 7) self.assertEqual(self.ks5.constituents().count(), 9) @@ -199,9 +199,9 @@ class TestChangeOperations(EndpointTester): self.ks5D4.refresh_from_db() self.operation1.refresh_from_db() self.assertEqual(self.operation1.result, None) - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 0) self.assertEqual(self.ks4.constituents().count(), 4) self.assertEqual(self.ks5.constituents().count(), 7) @@ -220,9 +220,9 @@ class TestChangeOperations(EndpointTester): self.executeOK(data=data, item=self.owned_id) self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 0) self.assertEqual(self.ks4.constituents().count(), 4) self.assertEqual(self.ks5.constituents().count(), 7) @@ -241,9 +241,9 @@ class TestChangeOperations(EndpointTester): self.executeOK(data=data, item=self.owned_id) self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks4.constituents().count(), 6) self.assertEqual(self.ks5.constituents().count(), 8) @@ -275,9 +275,9 @@ class TestChangeOperations(EndpointTester): self.executeOK(data=data, item=self.owned_id) self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 2) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks4.constituents().count(), 5) self.assertEqual(self.ks5.constituents().count(), 7) @@ -300,9 +300,9 @@ class TestChangeOperations(EndpointTester): self.executeOK(data=data, item=self.owned_id) self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks4.constituents().count(), 4) self.assertEqual(self.ks5.constituents().count(), 6) @@ -313,9 +313,9 @@ class TestChangeOperations(EndpointTester): self.executeOK(data=data, item=self.owned_id) self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks4.constituents().count(), 7) self.assertEqual(self.ks5.constituents().count(), 9) diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py index 41cd1533..943e56a6 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py @@ -125,11 +125,11 @@ class TestChangeSubstitutions(EndpointTester): self.ks4D1.refresh_from_db() self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 1) self.assertEqual(subs1_2.first().original, self.ks1X2) self.assertEqual(subs1_2.first().substitution, self.ks2S1) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(subs3_4.first().original, self.ks4S1) self.assertEqual(subs3_4.first().substitution, self.ks3X1) @@ -147,11 +147,11 @@ class TestChangeSubstitutions(EndpointTester): self.ks4D1.refresh_from_db() self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 1) self.assertEqual(subs1_2.first().original, self.ks1X1) self.assertEqual(subs1_2.first().substitution, self.ks2X1) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(subs3_4.first().original, self.ks4X1) self.assertEqual(subs3_4.first().substitution, self.ks3X1) @@ -165,9 +165,9 @@ class TestChangeSubstitutions(EndpointTester): self.executeOK(data=data, schema=self.ks1.model.pk) self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks5.constituents().count(), 7) self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 S1 DEL') @@ -180,9 +180,9 @@ class TestChangeSubstitutions(EndpointTester): self.ks4D1.refresh_from_db() self.ks4D2.refresh_from_db() self.ks5D4.refresh_from_db() - subs1_2 = self.operation4.getSubstitutions() + subs1_2 = self.operation4.getQ_substitutions() self.assertEqual(subs1_2.count(), 0) - subs3_4 = self.operation5.getSubstitutions() + subs3_4 = self.operation5.getQ_substitutions() self.assertEqual(subs3_4.count(), 1) self.assertEqual(self.ks5.constituents().count(), 7) self.assertEqual(self.ks4D1.definition_formal, r'X4 X1') diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py index e39dc221..f019e752 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py @@ -244,7 +244,7 @@ class TestOssViewset(EndpointTester): self.assertEqual(schema.visible, False) self.assertEqual(schema.access_policy, self.owned.model.access_policy) self.assertEqual(schema.location, self.owned.model.location) - self.assertIn(self.user2, schema.editors()) + self.assertIn(self.user2, schema.getQ_editors()) @decl_endpoint('/api/oss/{item}/delete-operation', method='patch') def test_delete_operation(self): @@ -409,12 +409,12 @@ class TestOssViewset(EndpointTester): self.assertEqual(self.operation3.alias, data['item_data']['alias']) self.assertEqual(self.operation3.title, data['item_data']['title']) self.assertEqual(self.operation3.comment, data['item_data']['comment']) - args = self.operation3.getArguments().order_by('order') + args = self.operation3.getQ_arguments().order_by('order') self.assertEqual(args[0].argument.pk, data['arguments'][0]) self.assertEqual(args[0].order, 0) self.assertEqual(args[1].argument.pk, data['arguments'][1]) self.assertEqual(args[1].order, 1) - sub = self.operation3.getSubstitutions()[0] + sub = self.operation3.getQ_substitutions()[0] self.assertEqual(sub.original.pk, data['substitutions'][0]['original']) self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution']) diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index dc929b60..4c056540 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -162,7 +162,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev oss = m.OperationSchema(self.get_object()) operation = cast(m.Operation, serializer.validated_data['target']) - old_schema: Optional[LibraryItem] = operation.result + old_schema = operation.result with transaction.atomic(): oss.update_positions(serializer.validated_data['positions']) oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents']) @@ -255,7 +255,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev 'input': msg.operationInputAlreadyConnected() }) oss = m.OperationSchema(self.get_object()) - old_schema: Optional[LibraryItem] = target_operation.result + old_schema = target_operation.result with transaction.atomic(): if old_schema is not None: if old_schema.is_synced(oss.model): @@ -370,10 +370,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev serializer = CstTargetSerializer(data=request.data) serializer.is_valid(raise_exception=True) cst = cast(Constituenta, serializer.validated_data['target']) - inheritance = m.Inheritance.objects.filter(child=cst) - while inheritance.exists(): - cst = cast(m.Inheritance, inheritance.first()).parent - inheritance = m.Inheritance.objects.filter(child=cst) + inheritance_query = m.Inheritance.objects.filter(child=cst) + while inheritance_query.exists(): + inheritance = inheritance_query.first() + if inheritance is None: + break + cst = inheritance.parent + inheritance_query = m.Inheritance.objects.filter(child=cst) return Response( status=c.HTTP_200_OK, diff --git a/rsconcept/backend/apps/rsform/models/Constituenta.py b/rsconcept/backend/apps/rsform/models/Constituenta.py index 9a1058cc..8acffe2f 100644 --- a/rsconcept/backend/apps/rsform/models/Constituenta.py +++ b/rsconcept/backend/apps/rsform/models/Constituenta.py @@ -49,56 +49,56 @@ class CstType(TextChoices): class Constituenta(Model): ''' Constituenta is the base unit for every conceptual schema. ''' - schema: ForeignKey = ForeignKey( + schema = ForeignKey( verbose_name='Концептуальная схема', to='library.LibraryItem', on_delete=CASCADE ) - order: PositiveIntegerField = PositiveIntegerField( + order = PositiveIntegerField( verbose_name='Позиция', default=0, ) - alias: CharField = CharField( + alias = CharField( verbose_name='Имя', max_length=8, default='undefined' ) - cst_type: CharField = CharField( + cst_type = CharField( verbose_name='Тип', max_length=10, choices=CstType.choices, default=CstType.BASE ) - convention: TextField = TextField( + convention = TextField( verbose_name='Комментарий/Конвенция', default='', blank=True ) - term_raw: TextField = TextField( + term_raw = TextField( verbose_name='Термин (с отсылками)', default='', blank=True ) - term_resolved: TextField = TextField( + term_resolved = TextField( verbose_name='Термин', default='', blank=True ) - term_forms: JSONField = JSONField( + term_forms = JSONField( verbose_name='Словоформы', default=list ) - definition_formal: TextField = TextField( + definition_formal = TextField( verbose_name='Родоструктурное определение', default='', blank=True ) - definition_raw: TextField = TextField( + definition_raw = TextField( verbose_name='Текстовое определение (с отсылками)', default='', blank=True ) - definition_resolved: TextField = TextField( + definition_resolved = TextField( verbose_name='Текстовое определение', default='', blank=True diff --git a/rsconcept/backend/apps/rsform/models/RSForm.py b/rsconcept/backend/apps/rsform/models/RSForm.py index f4c91365..bdc9171f 100644 --- a/rsconcept/backend/apps/rsform/models/RSForm.py +++ b/rsconcept/backend/apps/rsform/models/RSForm.py @@ -121,7 +121,7 @@ class RSForm: update_list.append(cst) Constituenta.objects.bulk_update(update_list, ['definition_resolved']) - def get_max_index(self, cst_type: CstType) -> int: + def get_max_index(self, cst_type: str) -> int: ''' Get maximum alias index for specific CstType. ''' result: int = 0 cst_list: Iterable[Constituenta] = [] diff --git a/rsconcept/backend/apps/rsform/models/api_RSLanguage.py b/rsconcept/backend/apps/rsform/models/api_RSLanguage.py index 9968f8a7..32b802d7 100644 --- a/rsconcept/backend/apps/rsform/models/api_RSLanguage.py +++ b/rsconcept/backend/apps/rsform/models/api_RSLanguage.py @@ -26,7 +26,7 @@ class TokenType(IntEnum): REDUCE = 299 -def get_type_prefix(cst_type: CstType) -> str: +def get_type_prefix(cst_type: str) -> str: ''' Get alias prefix. ''' match cst_type: case CstType.BASE: return 'X' @@ -40,7 +40,7 @@ def get_type_prefix(cst_type: CstType) -> str: return 'X' -def is_basic_concept(cst_type: CstType) -> bool: +def is_basic_concept(cst_type: str) -> bool: ''' Evaluate if CstType is basic concept.''' return cst_type in [ CstType.BASE, @@ -50,7 +50,7 @@ def is_basic_concept(cst_type: CstType) -> bool: ] -def is_base_set(cst_type: CstType) -> bool: +def is_base_set(cst_type: str) -> bool: ''' Evaluate if CstType is base set or constant set.''' return cst_type in [ CstType.BASE, @@ -58,7 +58,7 @@ def is_base_set(cst_type: CstType) -> bool: ] -def is_functional(cst_type: CstType) -> bool: +def is_functional(cst_type: str) -> bool: ''' Evaluate if CstType is function.''' return cst_type in [ CstType.FUNCTION, @@ -70,7 +70,7 @@ def guess_type(alias: str) -> CstType: ''' Get CstType for alias. ''' prefix = alias[0] for (value, _) in CstType.choices: - if prefix == get_type_prefix(cast(CstType, value)): + if prefix == get_type_prefix(value): return cast(CstType, value) return CstType.BASE diff --git a/rsconcept/backend/mypy.ini b/rsconcept/backend/mypy.ini index 519f4292..3ec11395 100644 --- a/rsconcept/backend/mypy.ini +++ b/rsconcept/backend/mypy.ini @@ -4,7 +4,8 @@ warn_return_any = True warn_unused_configs = True -plugins = mypy_django_plugin.main +plugins = + mypy_django_plugin.main # Per-module options: [mypy.plugins.django-stubs] diff --git a/rsconcept/backend/requirements-dev-lock.txt b/rsconcept/backend/requirements-dev-lock.txt new file mode 100644 index 00000000..4e705092 --- /dev/null +++ b/rsconcept/backend/requirements-dev-lock.txt @@ -0,0 +1,20 @@ +tzdata==2024.1 +Django==5.1.1 +djangorestframework==3.15.2 +django-cors-headers==4.4.0 +django-filter==24.3 +drf-spectacular==0.27.2 +drf-spectacular-sidecar==2024.7.1 +coreapi==2.3.3 +django-rest-passwordreset==1.4.1 +cctext==0.1.4 +pyconcept==0.1.6 + +psycopg2-binary==2.9.9 +gunicorn==23.0.0 + +djangorestframework-stubs==3.15.1 +django-extensions==3.2.3 +mypy==1.11.2 +pylint==3.2.7 +coverage==7.6.1 \ No newline at end of file diff --git a/rsconcept/backend/requirements.txt b/rsconcept/backend/requirements.txt index 0e75d5ed..1a11e390 100644 --- a/rsconcept/backend/requirements.txt +++ b/rsconcept/backend/requirements.txt @@ -1,5 +1,5 @@ tzdata==2024.1 -Django==5.1 +Django==5.1.1 djangorestframework==3.15.2 django-cors-headers==4.4.0 django-filter==24.3