mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
R: Improve backend type hints, linting and package locks
Some checks failed
Backend CI / build (3.12) (push) Has been cancelled
Some checks failed
Backend CI / build (3.12) (push) Has been cancelled
This commit is contained in:
parent
bc166b962e
commit
0211e2f58a
5
.github/workflows/backend.yml
vendored
5
.github/workflows/backend.yml
vendored
|
@ -15,7 +15,6 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
max-parallel: 4
|
max-parallel: 4
|
||||||
|
@ -31,13 +30,13 @@ jobs:
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev-lock.txt
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: |
|
run: |
|
||||||
pylint project apps
|
pylint project apps
|
||||||
mypy project apps
|
mypy project apps
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
if: '!cancelled()'
|
if: "!cancelled()"
|
||||||
run: |
|
run: |
|
||||||
python manage.py check
|
python manage.py check
|
||||||
python manage.py test
|
python manage.py test
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
''' Admin view: Library. '''
|
''' Admin view: Library. '''
|
||||||
|
from typing import cast
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
@ -23,7 +24,7 @@ class LibraryTemplateAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def alias(self, template: models.LibraryTemplate):
|
def alias(self, template: models.LibraryTemplate):
|
||||||
if template.lib_source:
|
if template.lib_source:
|
||||||
return template.lib_source.alias
|
return cast(models.LibraryItem, template.lib_source).alias
|
||||||
else:
|
else:
|
||||||
return 'N/A'
|
return 'N/A'
|
||||||
|
|
||||||
|
|
|
@ -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='Владелец'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -9,17 +9,17 @@ from apps.users.models import User
|
||||||
|
|
||||||
class Editor(Model):
|
class Editor(Model):
|
||||||
''' Editor list. '''
|
''' Editor list. '''
|
||||||
item: ForeignKey = ForeignKey(
|
item = ForeignKey(
|
||||||
verbose_name='Схема',
|
verbose_name='Схема',
|
||||||
to='library.LibraryItem',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
)
|
)
|
||||||
editor: ForeignKey = ForeignKey(
|
editor = ForeignKey(
|
||||||
verbose_name='Редактор',
|
verbose_name='Редактор',
|
||||||
to=User,
|
to=User,
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
)
|
)
|
||||||
time_create: DateTimeField = DateTimeField(
|
time_create = DateTimeField(
|
||||||
verbose_name='Дата добавления',
|
verbose_name='Дата добавления',
|
||||||
auto_now_add=True
|
auto_now_add=True
|
||||||
)
|
)
|
||||||
|
|
|
@ -48,55 +48,56 @@ def validate_location(target: str) -> bool:
|
||||||
|
|
||||||
class LibraryItem(Model):
|
class LibraryItem(Model):
|
||||||
''' Abstract library item.'''
|
''' Abstract library item.'''
|
||||||
item_type: CharField = CharField(
|
item_type = CharField(
|
||||||
verbose_name='Тип',
|
verbose_name='Тип',
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=LibraryItemType.choices,
|
choices=LibraryItemType.choices,
|
||||||
default=LibraryItemType.RSFORM
|
default=LibraryItemType.RSFORM
|
||||||
)
|
)
|
||||||
owner: ForeignKey = ForeignKey(
|
owner = ForeignKey(
|
||||||
verbose_name='Владелец',
|
verbose_name='Владелец',
|
||||||
to=User,
|
to=User,
|
||||||
on_delete=SET_NULL,
|
on_delete=SET_NULL,
|
||||||
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
title: TextField = TextField(
|
title = TextField(
|
||||||
verbose_name='Название'
|
verbose_name='Название'
|
||||||
)
|
)
|
||||||
alias: CharField = CharField(
|
alias = CharField(
|
||||||
verbose_name='Шифр',
|
verbose_name='Шифр',
|
||||||
max_length=255,
|
max_length=255,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
comment: TextField = TextField(
|
comment = TextField(
|
||||||
verbose_name='Комментарий',
|
verbose_name='Комментарий',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
visible: BooleanField = BooleanField(
|
visible = BooleanField(
|
||||||
verbose_name='Отображаемая',
|
verbose_name='Отображаемая',
|
||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
read_only: BooleanField = BooleanField(
|
read_only = BooleanField(
|
||||||
verbose_name='Запретить редактирование',
|
verbose_name='Запретить редактирование',
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
access_policy: CharField = CharField(
|
access_policy = CharField(
|
||||||
verbose_name='Политика доступа',
|
verbose_name='Политика доступа',
|
||||||
max_length=500,
|
max_length=500,
|
||||||
choices=AccessPolicy.choices,
|
choices=AccessPolicy.choices,
|
||||||
default=AccessPolicy.PUBLIC
|
default=AccessPolicy.PUBLIC
|
||||||
)
|
)
|
||||||
location: TextField = TextField(
|
location = TextField(
|
||||||
verbose_name='Расположение',
|
verbose_name='Расположение',
|
||||||
max_length=500,
|
max_length=500,
|
||||||
default=LocationHead.USER
|
default=LocationHead.USER
|
||||||
)
|
)
|
||||||
|
|
||||||
time_create: DateTimeField = DateTimeField(
|
time_create = DateTimeField(
|
||||||
verbose_name='Дата создания',
|
verbose_name='Дата создания',
|
||||||
auto_now_add=True
|
auto_now_add=True
|
||||||
)
|
)
|
||||||
time_update: DateTimeField = DateTimeField(
|
time_update = DateTimeField(
|
||||||
verbose_name='Дата изменения',
|
verbose_name='Дата изменения',
|
||||||
auto_now=True
|
auto_now=True
|
||||||
)
|
)
|
||||||
|
@ -112,11 +113,11 @@ class LibraryItem(Model):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return f'/api/library/{self.pk}'
|
return f'/api/library/{self.pk}'
|
||||||
|
|
||||||
def editors(self) -> QuerySet[User]:
|
def getQ_editors(self) -> QuerySet[User]:
|
||||||
''' Get all Editors of this item. '''
|
''' Get all Editors of this item. '''
|
||||||
return User.objects.filter(editor__item=self.pk)
|
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. '''
|
''' Get all Versions of this item. '''
|
||||||
return Version.objects.filter(item=self.pk).order_by('-time_create')
|
return Version.objects.filter(item=self.pk).order_by('-time_create')
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.db.models import CASCADE, ForeignKey, Model
|
||||||
|
|
||||||
class LibraryTemplate(Model):
|
class LibraryTemplate(Model):
|
||||||
''' Template for library items and constituents. '''
|
''' Template for library items and constituents. '''
|
||||||
lib_source: ForeignKey = ForeignKey(
|
lib_source = ForeignKey(
|
||||||
verbose_name='Источник',
|
verbose_name='Источник',
|
||||||
to='library.LibraryItem',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
|
|
|
@ -12,7 +12,7 @@ from django.db.models import (
|
||||||
|
|
||||||
class Version(Model):
|
class Version(Model):
|
||||||
''' Library item version archive. '''
|
''' Library item version archive. '''
|
||||||
item: ForeignKey = ForeignKey(
|
item = ForeignKey(
|
||||||
verbose_name='Схема',
|
verbose_name='Схема',
|
||||||
to='library.LibraryItem',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
|
@ -22,14 +22,14 @@ class Version(Model):
|
||||||
max_length=20,
|
max_length=20,
|
||||||
blank=False
|
blank=False
|
||||||
)
|
)
|
||||||
description: TextField = TextField(
|
description = TextField(
|
||||||
verbose_name='Описание',
|
verbose_name='Описание',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
data: JSONField = JSONField(
|
data = JSONField(
|
||||||
verbose_name='Содержание'
|
verbose_name='Содержание'
|
||||||
)
|
)
|
||||||
time_create: DateTimeField = DateTimeField(
|
time_create = DateTimeField(
|
||||||
verbose_name='Дата создания',
|
verbose_name='Дата создания',
|
||||||
auto_now_add=True
|
auto_now_add=True
|
||||||
)
|
)
|
||||||
|
|
|
@ -84,10 +84,10 @@ class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = ('owner', 'id', 'item_type')
|
read_only_fields = ('owner', 'id', 'item_type')
|
||||||
|
|
||||||
def get_editors(self, instance: LibraryItem) -> list[int]:
|
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:
|
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):
|
class UserTargetSerializer(serializers.Serializer):
|
||||||
|
|
|
@ -35,64 +35,64 @@ class TestEditor(TestCase):
|
||||||
|
|
||||||
def test_add_editor(self):
|
def test_add_editor(self):
|
||||||
self.assertTrue(Editor.add(self.item.pk, self.user1.pk))
|
self.assertTrue(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(self.user1 in list(self.item.editors()))
|
self.assertTrue(self.user1 in list(self.item.getQ_editors()))
|
||||||
|
|
||||||
self.assertFalse(Editor.add(self.item.pk, self.user1.pk))
|
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.assertTrue(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(self.user1 in self.item.editors())
|
self.assertTrue(self.user1 in self.item.getQ_editors())
|
||||||
self.assertTrue(self.user2 in self.item.editors())
|
self.assertTrue(self.user2 in self.item.getQ_editors())
|
||||||
|
|
||||||
self.user1.delete()
|
self.user1.delete()
|
||||||
self.assertEqual(self.item.editors().count(), 1)
|
self.assertEqual(self.item.getQ_editors().count(), 1)
|
||||||
|
|
||||||
|
|
||||||
def test_remove_editor(self):
|
def test_remove_editor(self):
|
||||||
self.assertFalse(Editor.remove(self.item.pk, self.user1.pk))
|
self.assertFalse(Editor.remove(self.item.pk, self.user1.pk))
|
||||||
Editor.add(self.item.pk, self.user1.pk)
|
Editor.add(self.item.pk, self.user1.pk)
|
||||||
Editor.add(self.item.pk, self.user2.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.assertTrue(Editor.remove(self.item.pk, self.user1.pk))
|
||||||
self.assertEqual(self.item.editors().count(), 1)
|
self.assertEqual(self.item.getQ_editors().count(), 1)
|
||||||
self.assertTrue(self.user2 in self.item.editors())
|
self.assertTrue(self.user2 in self.item.getQ_editors())
|
||||||
|
|
||||||
self.assertFalse(Editor.remove(self.item.pk, self.user1.pk))
|
self.assertFalse(Editor.remove(self.item.pk, self.user1.pk))
|
||||||
|
|
||||||
|
|
||||||
def test_set_editors(self):
|
def test_set_editors(self):
|
||||||
Editor.set(self.item.pk, [self.user1.pk])
|
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])
|
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, [])
|
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])
|
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):
|
def test_set_editors_return_diff(self):
|
||||||
added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk])
|
added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk])
|
||||||
self.assertEqual(added, [self.user1.pk])
|
self.assertEqual(added, [self.user1.pk])
|
||||||
self.assertEqual(deleted, [])
|
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])
|
added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk, self.user1.pk])
|
||||||
self.assertEqual(added, [])
|
self.assertEqual(added, [])
|
||||||
self.assertEqual(deleted, [])
|
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, [])
|
added, deleted = Editor.set_and_return_diff(self.item.pk, [])
|
||||||
self.assertEqual(added, [])
|
self.assertEqual(added, [])
|
||||||
self.assertEqual(deleted, [self.user1.pk])
|
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])
|
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(added, [self.user1.pk, self.user2.pk])
|
||||||
self.assertEqual(deleted, [])
|
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]))
|
||||||
|
|
|
@ -250,22 +250,22 @@ class TestLibraryViewset(EndpointTester):
|
||||||
self.executeOK(data=data, item=self.owned.pk)
|
self.executeOK(data=data, item=self.owned.pk)
|
||||||
self.owned.refresh_from_db()
|
self.owned.refresh_from_db()
|
||||||
self.assertEqual(self.owned.time_update, time_update)
|
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.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]}
|
data = {'users': [self.user2.pk]}
|
||||||
self.executeOK(data=data)
|
self.executeOK(data=data)
|
||||||
self.assertEqual(list(self.owned.editors()), [self.user2])
|
self.assertEqual(list(self.owned.getQ_editors()), [self.user2])
|
||||||
|
|
||||||
data = {'users': []}
|
data = {'users': []}
|
||||||
self.executeOK(data=data)
|
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]}
|
data = {'users': [self.user2.pk, self.user.pk]}
|
||||||
self.executeOK(data=data)
|
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')
|
@decl_endpoint('/api/library/{item}', method='delete')
|
||||||
|
|
|
@ -157,7 +157,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
clone = deepcopy(item)
|
clone = deepcopy(item)
|
||||||
clone.pk = None
|
clone.pk = None
|
||||||
clone.owner = self.request.user
|
clone.owner = cast(User, self.request.user)
|
||||||
clone.title = serializer.validated_data['title']
|
clone.title = serializer.validated_data['title']
|
||||||
clone.alias = serializer.validated_data.get('alias', '')
|
clone.alias = serializer.validated_data.get('alias', '')
|
||||||
clone.comment = serializer.validated_data.get('comment', '')
|
clone.comment = serializer.validated_data.get('comment', '')
|
||||||
|
|
|
@ -44,7 +44,7 @@ class VersionViewset(
|
||||||
def restore(self, request: Request, pk) -> HttpResponse:
|
def restore(self, request: Request, pk) -> HttpResponse:
|
||||||
''' Restore version data into current item. '''
|
''' Restore version data into current item. '''
|
||||||
version = cast(m.Version, self.get_object())
|
version = cast(m.Version, self.get_object())
|
||||||
item = cast(m.LibraryItem, version.item)
|
item = version.item
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
RSFormSerializer(item).restore_from_version(version.data)
|
RSFormSerializer(item).restore_from_version(version.data)
|
||||||
return Response(
|
return Response(
|
||||||
|
|
|
@ -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='Связанная КС'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,19 +4,19 @@ from django.db.models import CASCADE, ForeignKey, Model, PositiveIntegerField
|
||||||
|
|
||||||
class Argument(Model):
|
class Argument(Model):
|
||||||
''' Operation Argument.'''
|
''' Operation Argument.'''
|
||||||
operation: ForeignKey = ForeignKey(
|
operation = ForeignKey(
|
||||||
verbose_name='Операция',
|
verbose_name='Операция',
|
||||||
to='oss.Operation',
|
to='oss.Operation',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
related_name='arguments'
|
related_name='arguments'
|
||||||
)
|
)
|
||||||
argument: ForeignKey = ForeignKey(
|
argument = ForeignKey(
|
||||||
verbose_name='Аргумент',
|
verbose_name='Аргумент',
|
||||||
to='oss.Operation',
|
to='oss.Operation',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
related_name='descendants'
|
related_name='descendants'
|
||||||
)
|
)
|
||||||
order: PositiveIntegerField = PositiveIntegerField(
|
order = PositiveIntegerField(
|
||||||
verbose_name='Позиция',
|
verbose_name='Позиция',
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,19 +4,19 @@ from django.db.models import CASCADE, ForeignKey, Model
|
||||||
|
|
||||||
class Inheritance(Model):
|
class Inheritance(Model):
|
||||||
''' Inheritance links parent and child constituents in synthesis operation.'''
|
''' Inheritance links parent and child constituents in synthesis operation.'''
|
||||||
operation: ForeignKey = ForeignKey(
|
operation = ForeignKey(
|
||||||
verbose_name='Операция',
|
verbose_name='Операция',
|
||||||
to='oss.Operation',
|
to='oss.Operation',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
related_name='inheritances'
|
related_name='inheritances'
|
||||||
)
|
)
|
||||||
parent: ForeignKey = ForeignKey(
|
parent = ForeignKey(
|
||||||
verbose_name='Исходная конституента',
|
verbose_name='Исходная конституента',
|
||||||
to='rsform.Constituenta',
|
to='rsform.Constituenta',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
related_name='as_parent'
|
related_name='as_parent'
|
||||||
)
|
)
|
||||||
child: ForeignKey = ForeignKey(
|
child = ForeignKey(
|
||||||
verbose_name='Наследованная конституента',
|
verbose_name='Наследованная конституента',
|
||||||
to='rsform.Constituenta',
|
to='rsform.Constituenta',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
|
|
|
@ -23,45 +23,46 @@ class OperationType(TextChoices):
|
||||||
|
|
||||||
class Operation(Model):
|
class Operation(Model):
|
||||||
''' Operational schema Unit.'''
|
''' Operational schema Unit.'''
|
||||||
oss: ForeignKey = ForeignKey(
|
oss = ForeignKey(
|
||||||
verbose_name='Схема синтеза',
|
verbose_name='Схема синтеза',
|
||||||
to='library.LibraryItem',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
related_name='operations'
|
related_name='operations'
|
||||||
)
|
)
|
||||||
operation_type: CharField = CharField(
|
operation_type = CharField(
|
||||||
verbose_name='Тип',
|
verbose_name='Тип',
|
||||||
max_length=10,
|
max_length=10,
|
||||||
choices=OperationType.choices,
|
choices=OperationType.choices,
|
||||||
default=OperationType.INPUT
|
default=OperationType.INPUT
|
||||||
)
|
)
|
||||||
result: ForeignKey = ForeignKey(
|
result = ForeignKey(
|
||||||
verbose_name='Связанная КС',
|
verbose_name='Связанная КС',
|
||||||
to='library.LibraryItem',
|
to='library.LibraryItem',
|
||||||
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
on_delete=SET_NULL,
|
on_delete=SET_NULL,
|
||||||
related_name='producer'
|
related_name='producer'
|
||||||
)
|
)
|
||||||
|
|
||||||
alias: CharField = CharField(
|
alias = CharField(
|
||||||
verbose_name='Шифр',
|
verbose_name='Шифр',
|
||||||
max_length=255,
|
max_length=255,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
title: TextField = TextField(
|
title = TextField(
|
||||||
verbose_name='Название',
|
verbose_name='Название',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
comment: TextField = TextField(
|
comment = TextField(
|
||||||
verbose_name='Комментарий',
|
verbose_name='Комментарий',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
position_x: FloatField = FloatField(
|
position_x = FloatField(
|
||||||
verbose_name='Положение по горизонтали',
|
verbose_name='Положение по горизонтали',
|
||||||
default=0
|
default=0
|
||||||
)
|
)
|
||||||
position_y: FloatField = FloatField(
|
position_y = FloatField(
|
||||||
verbose_name='Положение по вертикали',
|
verbose_name='Положение по вертикали',
|
||||||
default=0
|
default=0
|
||||||
)
|
)
|
||||||
|
@ -74,10 +75,10 @@ class Operation(Model):
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f'Операция {self.alias}'
|
return f'Операция {self.alias}'
|
||||||
|
|
||||||
def getArguments(self) -> QuerySet[Argument]:
|
def getQ_arguments(self) -> QuerySet[Argument]:
|
||||||
''' Operation arguments. '''
|
''' Operation arguments. '''
|
||||||
return Argument.objects.filter(operation=self)
|
return Argument.objects.filter(operation=self)
|
||||||
|
|
||||||
def getSubstitutions(self) -> QuerySet[Substitution]:
|
def getQ_substitutions(self) -> QuerySet[Substitution]:
|
||||||
''' Operation substitutions. '''
|
''' Operation substitutions. '''
|
||||||
return Substitution.objects.filter(operation=self)
|
return Substitution.objects.filter(operation=self)
|
||||||
|
|
|
@ -121,7 +121,6 @@ class OperationSchema:
|
||||||
|
|
||||||
operation.result = schema
|
operation.result = schema
|
||||||
if schema is not None:
|
if schema is not None:
|
||||||
operation.result = schema
|
|
||||||
operation.alias = schema.alias
|
operation.alias = schema.alias
|
||||||
operation.title = schema.title
|
operation.title = schema.title
|
||||||
operation.comment = schema.comment
|
operation.comment = schema.comment
|
||||||
|
@ -139,7 +138,7 @@ class OperationSchema:
|
||||||
processed: list[Operation] = []
|
processed: list[Operation] = []
|
||||||
updated: list[Argument] = []
|
updated: list[Argument] = []
|
||||||
deleted: list[Argument] = []
|
deleted: list[Argument] = []
|
||||||
for current in operation.getArguments():
|
for current in operation.getQ_arguments():
|
||||||
if current.argument not in arguments:
|
if current.argument not in arguments:
|
||||||
deleted.append(current)
|
deleted.append(current)
|
||||||
else:
|
else:
|
||||||
|
@ -172,7 +171,7 @@ class OperationSchema:
|
||||||
schema = self.cache.get_schema(operation)
|
schema = self.cache.get_schema(operation)
|
||||||
processed: list[dict] = []
|
processed: list[dict] = []
|
||||||
deleted: list[Substitution] = []
|
deleted: list[Substitution] = []
|
||||||
for current in operation.getSubstitutions():
|
for current in operation.getQ_substitutions():
|
||||||
subs = [
|
subs = [
|
||||||
x for x in substitutes
|
x for x in substitutes
|
||||||
if x['original'] == current.original and x['substitution'] == current.substitution
|
if x['original'] == current.original and x['substitution'] == current.substitution
|
||||||
|
@ -215,7 +214,7 @@ class OperationSchema:
|
||||||
access_policy=self.model.access_policy,
|
access_policy=self.model.access_policy,
|
||||||
location=self.model.location
|
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.result = schema.model
|
||||||
operation.save()
|
operation.save()
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
|
@ -223,10 +222,14 @@ class OperationSchema:
|
||||||
|
|
||||||
def execute_operation(self, operation: Operation) -> bool:
|
def execute_operation(self, operation: Operation) -> bool:
|
||||||
''' Execute target operation. '''
|
''' Execute target operation. '''
|
||||||
schemas: list[LibraryItem] = [arg.argument.result for arg in operation.getArguments().order_by('order')]
|
schemas = [
|
||||||
if None in 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
|
return False
|
||||||
substitutions = operation.getSubstitutions()
|
substitutions = operation.getQ_substitutions()
|
||||||
receiver = self.create_input(self.cache.operation_by_id[operation.pk])
|
receiver = self.create_input(self.cache.operation_by_id[operation.pk])
|
||||||
|
|
||||||
parents: dict = {}
|
parents: dict = {}
|
||||||
|
@ -284,7 +287,7 @@ class OperationSchema:
|
||||||
''' Trigger cascade resolutions when constituenta type is changed. '''
|
''' Trigger cascade resolutions when constituenta type is changed. '''
|
||||||
self.cache.insert_schema(source)
|
self.cache.insert_schema(source)
|
||||||
operation = self.cache.get_operation(source.model.pk)
|
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:
|
def after_update_cst(self, source: RSForm, target: Constituenta, data: dict, old_data: dict) -> None:
|
||||||
''' Trigger cascade resolutions when constituenta data is changed. '''
|
''' 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)
|
substitution_id = self.cache.get_inheritor(substitution_cst.pk, operation_id)
|
||||||
assert substitution_id is not None
|
assert substitution_id is not None
|
||||||
substitution_inheritor = schema.cache.by_id[substitution_id]
|
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)
|
self._cascade_partial_mapping(mapping, dependant, operation_id, schema)
|
||||||
|
|
||||||
def _process_added_substitutions(self, schema: Optional[RSForm], added: list[Substitution]) -> None:
|
def _process_added_substitutions(self, schema: Optional[RSForm], added: list[Substitution]) -> None:
|
||||||
|
|
|
@ -4,19 +4,19 @@ from django.db.models import CASCADE, ForeignKey, Model
|
||||||
|
|
||||||
class Substitution(Model):
|
class Substitution(Model):
|
||||||
''' Substitutions as part of Synthesis operation in OSS.'''
|
''' Substitutions as part of Synthesis operation in OSS.'''
|
||||||
operation: ForeignKey = ForeignKey(
|
operation = ForeignKey(
|
||||||
verbose_name='Операция',
|
verbose_name='Операция',
|
||||||
to='oss.Operation',
|
to='oss.Operation',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
original: ForeignKey = ForeignKey(
|
original = ForeignKey(
|
||||||
verbose_name='Удаляемая конституента',
|
verbose_name='Удаляемая конституента',
|
||||||
to='rsform.Constituenta',
|
to='rsform.Constituenta',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
related_name='as_original'
|
related_name='as_original'
|
||||||
)
|
)
|
||||||
substitution: ForeignKey = ForeignKey(
|
substitution = ForeignKey(
|
||||||
verbose_name='Замещающая конституента',
|
verbose_name='Замещающая конституента',
|
||||||
to='rsform.Constituenta',
|
to='rsform.Constituenta',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
|
|
|
@ -53,6 +53,7 @@ class TestChangeAttributes(EndpointTester):
|
||||||
alias='3',
|
alias='3',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
|
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
|
||||||
self.owned.execute_operation(self.operation3)
|
self.owned.execute_operation(self.operation3)
|
||||||
self.operation3.refresh_from_db()
|
self.operation3.refresh_from_db()
|
||||||
self.ks3 = RSForm(self.operation3.result)
|
self.ks3 = RSForm(self.operation3.result)
|
||||||
|
@ -116,10 +117,10 @@ class TestChangeAttributes(EndpointTester):
|
||||||
self.ks1.refresh_from_db()
|
self.ks1.refresh_from_db()
|
||||||
self.ks2.refresh_from_db()
|
self.ks2.refresh_from_db()
|
||||||
self.ks3.refresh_from_db()
|
self.ks3.refresh_from_db()
|
||||||
self.assertEqual(list(self.owned.model.editors()), [self.user3])
|
self.assertEqual(list(self.owned.model.getQ_editors()), [self.user3])
|
||||||
self.assertEqual(list(self.ks1.model.editors()), [self.user, self.user2])
|
self.assertEqual(list(self.ks1.model.getQ_editors()), [self.user, self.user2])
|
||||||
self.assertEqual(list(self.ks2.model.editors()), [])
|
self.assertEqual(list(self.ks2.model.getQ_editors()), [])
|
||||||
self.assertEqual(set(self.ks3.model.editors()), set([self.user, self.user3]))
|
self.assertEqual(set(self.ks3.model.getQ_editors()), set([self.user, self.user3]))
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}', method='patch')
|
@decl_endpoint('/api/library/{item}', method='patch')
|
||||||
def test_sync_from_result(self):
|
def test_sync_from_result(self):
|
||||||
|
|
|
@ -124,9 +124,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.ks4D1.refresh_from_db()
|
self.ks4D1.refresh_from_db()
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 6)
|
self.assertEqual(self.ks5.constituents().count(), 6)
|
||||||
|
@ -147,9 +147,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.ks5D4.refresh_from_db()
|
self.ks5D4.refresh_from_db()
|
||||||
self.operation2.refresh_from_db()
|
self.operation2.refresh_from_db()
|
||||||
self.assertEqual(self.operation2.result, None)
|
self.assertEqual(self.operation2.result, None)
|
||||||
subs1_2 = self.operation4.getSubstitutions()
|
subs1_2 = self.operation4.getQ_substitutions()
|
||||||
self.assertEqual(subs1_2.count(), 0)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 6)
|
self.assertEqual(self.ks5.constituents().count(), 6)
|
||||||
|
@ -181,9 +181,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.operation2.refresh_from_db()
|
self.operation2.refresh_from_db()
|
||||||
self.assertEqual(self.operation2.result, ks6.model)
|
self.assertEqual(self.operation2.result, ks6.model)
|
||||||
self.assertEqual(self.operation2.alias, ks6.model.alias)
|
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)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 7)
|
self.assertEqual(self.ks4.constituents().count(), 7)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 9)
|
self.assertEqual(self.ks5.constituents().count(), 9)
|
||||||
|
@ -199,9 +199,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.ks5D4.refresh_from_db()
|
self.ks5D4.refresh_from_db()
|
||||||
self.operation1.refresh_from_db()
|
self.operation1.refresh_from_db()
|
||||||
self.assertEqual(self.operation1.result, None)
|
self.assertEqual(self.operation1.result, None)
|
||||||
subs1_2 = self.operation4.getSubstitutions()
|
subs1_2 = self.operation4.getQ_substitutions()
|
||||||
self.assertEqual(subs1_2.count(), 0)
|
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(subs3_4.count(), 0)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||||
|
@ -220,9 +220,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.executeOK(data=data, item=self.owned_id)
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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)
|
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(subs3_4.count(), 0)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||||
|
@ -241,9 +241,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.executeOK(data=data, item=self.owned_id)
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 6)
|
self.assertEqual(self.ks4.constituents().count(), 6)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 8)
|
self.assertEqual(self.ks5.constituents().count(), 8)
|
||||||
|
@ -275,9 +275,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.executeOK(data=data, item=self.owned_id)
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 5)
|
self.assertEqual(self.ks4.constituents().count(), 5)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||||
|
@ -300,9 +300,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.executeOK(data=data, item=self.owned_id)
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 6)
|
self.assertEqual(self.ks5.constituents().count(), 6)
|
||||||
|
@ -313,9 +313,9 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.executeOK(data=data, item=self.owned_id)
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks4.constituents().count(), 7)
|
self.assertEqual(self.ks4.constituents().count(), 7)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 9)
|
self.assertEqual(self.ks5.constituents().count(), 9)
|
||||||
|
|
|
@ -125,11 +125,11 @@ class TestChangeSubstitutions(EndpointTester):
|
||||||
self.ks4D1.refresh_from_db()
|
self.ks4D1.refresh_from_db()
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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.count(), 1)
|
||||||
self.assertEqual(subs1_2.first().original, self.ks1X2)
|
self.assertEqual(subs1_2.first().original, self.ks1X2)
|
||||||
self.assertEqual(subs1_2.first().substitution, self.ks2S1)
|
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.count(), 1)
|
||||||
self.assertEqual(subs3_4.first().original, self.ks4S1)
|
self.assertEqual(subs3_4.first().original, self.ks4S1)
|
||||||
self.assertEqual(subs3_4.first().substitution, self.ks3X1)
|
self.assertEqual(subs3_4.first().substitution, self.ks3X1)
|
||||||
|
@ -147,11 +147,11 @@ class TestChangeSubstitutions(EndpointTester):
|
||||||
self.ks4D1.refresh_from_db()
|
self.ks4D1.refresh_from_db()
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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.count(), 1)
|
||||||
self.assertEqual(subs1_2.first().original, self.ks1X1)
|
self.assertEqual(subs1_2.first().original, self.ks1X1)
|
||||||
self.assertEqual(subs1_2.first().substitution, self.ks2X1)
|
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.count(), 1)
|
||||||
self.assertEqual(subs3_4.first().original, self.ks4X1)
|
self.assertEqual(subs3_4.first().original, self.ks4X1)
|
||||||
self.assertEqual(subs3_4.first().substitution, self.ks3X1)
|
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.executeOK(data=data, schema=self.ks1.model.pk)
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 S1 DEL')
|
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.ks4D1.refresh_from_db()
|
||||||
self.ks4D2.refresh_from_db()
|
self.ks4D2.refresh_from_db()
|
||||||
self.ks5D4.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)
|
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(subs3_4.count(), 1)
|
||||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||||
self.assertEqual(self.ks4D1.definition_formal, r'X4 X1')
|
self.assertEqual(self.ks4D1.definition_formal, r'X4 X1')
|
||||||
|
|
|
@ -244,7 +244,7 @@ class TestOssViewset(EndpointTester):
|
||||||
self.assertEqual(schema.visible, False)
|
self.assertEqual(schema.visible, False)
|
||||||
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
||||||
self.assertEqual(schema.location, self.owned.model.location)
|
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')
|
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||||
def test_delete_operation(self):
|
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.alias, data['item_data']['alias'])
|
||||||
self.assertEqual(self.operation3.title, data['item_data']['title'])
|
self.assertEqual(self.operation3.title, data['item_data']['title'])
|
||||||
self.assertEqual(self.operation3.comment, data['item_data']['comment'])
|
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].argument.pk, data['arguments'][0])
|
||||||
self.assertEqual(args[0].order, 0)
|
self.assertEqual(args[0].order, 0)
|
||||||
self.assertEqual(args[1].argument.pk, data['arguments'][1])
|
self.assertEqual(args[1].argument.pk, data['arguments'][1])
|
||||||
self.assertEqual(args[1].order, 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.original.pk, data['substitutions'][0]['original'])
|
||||||
self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution'])
|
self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution'])
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
|
|
||||||
oss = m.OperationSchema(self.get_object())
|
oss = m.OperationSchema(self.get_object())
|
||||||
operation = cast(m.Operation, serializer.validated_data['target'])
|
operation = cast(m.Operation, serializer.validated_data['target'])
|
||||||
old_schema: Optional[LibraryItem] = operation.result
|
old_schema = operation.result
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
oss.update_positions(serializer.validated_data['positions'])
|
oss.update_positions(serializer.validated_data['positions'])
|
||||||
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
|
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()
|
'input': msg.operationInputAlreadyConnected()
|
||||||
})
|
})
|
||||||
oss = m.OperationSchema(self.get_object())
|
oss = m.OperationSchema(self.get_object())
|
||||||
old_schema: Optional[LibraryItem] = target_operation.result
|
old_schema = target_operation.result
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if old_schema is not None:
|
if old_schema is not None:
|
||||||
if old_schema.is_synced(oss.model):
|
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 = CstTargetSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
cst = cast(Constituenta, serializer.validated_data['target'])
|
cst = cast(Constituenta, serializer.validated_data['target'])
|
||||||
inheritance = m.Inheritance.objects.filter(child=cst)
|
inheritance_query = m.Inheritance.objects.filter(child=cst)
|
||||||
while inheritance.exists():
|
while inheritance_query.exists():
|
||||||
cst = cast(m.Inheritance, inheritance.first()).parent
|
inheritance = inheritance_query.first()
|
||||||
inheritance = m.Inheritance.objects.filter(child=cst)
|
if inheritance is None:
|
||||||
|
break
|
||||||
|
cst = inheritance.parent
|
||||||
|
inheritance_query = m.Inheritance.objects.filter(child=cst)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
|
|
|
@ -49,56 +49,56 @@ class CstType(TextChoices):
|
||||||
|
|
||||||
class Constituenta(Model):
|
class Constituenta(Model):
|
||||||
''' Constituenta is the base unit for every conceptual schema. '''
|
''' Constituenta is the base unit for every conceptual schema. '''
|
||||||
schema: ForeignKey = ForeignKey(
|
schema = ForeignKey(
|
||||||
verbose_name='Концептуальная схема',
|
verbose_name='Концептуальная схема',
|
||||||
to='library.LibraryItem',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
)
|
)
|
||||||
order: PositiveIntegerField = PositiveIntegerField(
|
order = PositiveIntegerField(
|
||||||
verbose_name='Позиция',
|
verbose_name='Позиция',
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
alias: CharField = CharField(
|
alias = CharField(
|
||||||
verbose_name='Имя',
|
verbose_name='Имя',
|
||||||
max_length=8,
|
max_length=8,
|
||||||
default='undefined'
|
default='undefined'
|
||||||
)
|
)
|
||||||
cst_type: CharField = CharField(
|
cst_type = CharField(
|
||||||
verbose_name='Тип',
|
verbose_name='Тип',
|
||||||
max_length=10,
|
max_length=10,
|
||||||
choices=CstType.choices,
|
choices=CstType.choices,
|
||||||
default=CstType.BASE
|
default=CstType.BASE
|
||||||
)
|
)
|
||||||
convention: TextField = TextField(
|
convention = TextField(
|
||||||
verbose_name='Комментарий/Конвенция',
|
verbose_name='Комментарий/Конвенция',
|
||||||
default='',
|
default='',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
term_raw: TextField = TextField(
|
term_raw = TextField(
|
||||||
verbose_name='Термин (с отсылками)',
|
verbose_name='Термин (с отсылками)',
|
||||||
default='',
|
default='',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
term_resolved: TextField = TextField(
|
term_resolved = TextField(
|
||||||
verbose_name='Термин',
|
verbose_name='Термин',
|
||||||
default='',
|
default='',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
term_forms: JSONField = JSONField(
|
term_forms = JSONField(
|
||||||
verbose_name='Словоформы',
|
verbose_name='Словоформы',
|
||||||
default=list
|
default=list
|
||||||
)
|
)
|
||||||
definition_formal: TextField = TextField(
|
definition_formal = TextField(
|
||||||
verbose_name='Родоструктурное определение',
|
verbose_name='Родоструктурное определение',
|
||||||
default='',
|
default='',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
definition_raw: TextField = TextField(
|
definition_raw = TextField(
|
||||||
verbose_name='Текстовое определение (с отсылками)',
|
verbose_name='Текстовое определение (с отсылками)',
|
||||||
default='',
|
default='',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
definition_resolved: TextField = TextField(
|
definition_resolved = TextField(
|
||||||
verbose_name='Текстовое определение',
|
verbose_name='Текстовое определение',
|
||||||
default='',
|
default='',
|
||||||
blank=True
|
blank=True
|
||||||
|
|
|
@ -121,7 +121,7 @@ class RSForm:
|
||||||
update_list.append(cst)
|
update_list.append(cst)
|
||||||
Constituenta.objects.bulk_update(update_list, ['definition_resolved'])
|
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. '''
|
''' Get maximum alias index for specific CstType. '''
|
||||||
result: int = 0
|
result: int = 0
|
||||||
cst_list: Iterable[Constituenta] = []
|
cst_list: Iterable[Constituenta] = []
|
||||||
|
|
|
@ -26,7 +26,7 @@ class TokenType(IntEnum):
|
||||||
REDUCE = 299
|
REDUCE = 299
|
||||||
|
|
||||||
|
|
||||||
def get_type_prefix(cst_type: CstType) -> str:
|
def get_type_prefix(cst_type: str) -> str:
|
||||||
''' Get alias prefix. '''
|
''' Get alias prefix. '''
|
||||||
match cst_type:
|
match cst_type:
|
||||||
case CstType.BASE: return 'X'
|
case CstType.BASE: return 'X'
|
||||||
|
@ -40,7 +40,7 @@ def get_type_prefix(cst_type: CstType) -> str:
|
||||||
return 'X'
|
return 'X'
|
||||||
|
|
||||||
|
|
||||||
def is_basic_concept(cst_type: CstType) -> bool:
|
def is_basic_concept(cst_type: str) -> bool:
|
||||||
''' Evaluate if CstType is basic concept.'''
|
''' Evaluate if CstType is basic concept.'''
|
||||||
return cst_type in [
|
return cst_type in [
|
||||||
CstType.BASE,
|
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.'''
|
''' Evaluate if CstType is base set or constant set.'''
|
||||||
return cst_type in [
|
return cst_type in [
|
||||||
CstType.BASE,
|
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.'''
|
''' Evaluate if CstType is function.'''
|
||||||
return cst_type in [
|
return cst_type in [
|
||||||
CstType.FUNCTION,
|
CstType.FUNCTION,
|
||||||
|
@ -70,7 +70,7 @@ def guess_type(alias: str) -> CstType:
|
||||||
''' Get CstType for alias. '''
|
''' Get CstType for alias. '''
|
||||||
prefix = alias[0]
|
prefix = alias[0]
|
||||||
for (value, _) in CstType.choices:
|
for (value, _) in CstType.choices:
|
||||||
if prefix == get_type_prefix(cast(CstType, value)):
|
if prefix == get_type_prefix(value):
|
||||||
return cast(CstType, value)
|
return cast(CstType, value)
|
||||||
return CstType.BASE
|
return CstType.BASE
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
warn_return_any = True
|
warn_return_any = True
|
||||||
warn_unused_configs = True
|
warn_unused_configs = True
|
||||||
|
|
||||||
plugins = mypy_django_plugin.main
|
plugins =
|
||||||
|
mypy_django_plugin.main
|
||||||
|
|
||||||
# Per-module options:
|
# Per-module options:
|
||||||
[mypy.plugins.django-stubs]
|
[mypy.plugins.django-stubs]
|
||||||
|
|
20
rsconcept/backend/requirements-dev-lock.txt
Normal file
20
rsconcept/backend/requirements-dev-lock.txt
Normal file
|
@ -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
|
|
@ -1,5 +1,5 @@
|
||||||
tzdata==2024.1
|
tzdata==2024.1
|
||||||
Django==5.1
|
Django==5.1.1
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
django-cors-headers==4.4.0
|
django-cors-headers==4.4.0
|
||||||
django-filter==24.3
|
django-filter==24.3
|
||||||
|
|
Loading…
Reference in New Issue
Block a user