R: restructure backend
Warning! This will reset database migrations. Data should be imported manually
This commit is contained in:
parent
95caab2919
commit
90dcf7a8eb
0
rsconcept/backend/apps/library/__init__.py
Normal file
0
rsconcept/backend/apps/library/__init__.py
Normal file
62
rsconcept/backend/apps/library/admin.py
Normal file
62
rsconcept/backend/apps/library/admin.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
''' Admin view: Library. '''
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryItemAdmin(admin.ModelAdmin):
|
||||||
|
''' Admin model: LibraryItem. '''
|
||||||
|
date_hierarchy = 'time_update'
|
||||||
|
list_display = [
|
||||||
|
'alias', 'title', 'owner',
|
||||||
|
'visible', 'read_only', 'access_policy', 'location',
|
||||||
|
'time_update'
|
||||||
|
]
|
||||||
|
list_filter = ['visible', 'read_only', 'access_policy', 'location', 'time_update']
|
||||||
|
search_fields = ['alias', 'title', 'location']
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryTemplateAdmin(admin.ModelAdmin):
|
||||||
|
''' Admin model: LibraryTemplate. '''
|
||||||
|
list_display = ['id', 'alias']
|
||||||
|
list_select_related = ['lib_source']
|
||||||
|
|
||||||
|
def alias(self, template: models.LibraryTemplate):
|
||||||
|
if template.lib_source:
|
||||||
|
return template.lib_source.alias
|
||||||
|
else:
|
||||||
|
return 'N/A'
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionAdmin(admin.ModelAdmin):
|
||||||
|
''' Admin model: Subscriptions. '''
|
||||||
|
list_display = ['id', 'item', 'user']
|
||||||
|
search_fields = [
|
||||||
|
'item__title', 'item__alias',
|
||||||
|
'user__username', 'user__first_name', 'user__last_name'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EditorAdmin(admin.ModelAdmin):
|
||||||
|
''' Admin model: Editors. '''
|
||||||
|
list_display = ['id', 'item', 'editor']
|
||||||
|
search_fields = [
|
||||||
|
'item__title', 'item__alias',
|
||||||
|
'editor__username', 'editor__first_name', 'editor__last_name'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class VersionAdmin(admin.ModelAdmin):
|
||||||
|
''' Admin model: Versions. '''
|
||||||
|
list_display = ['id', 'item', 'version', 'description', 'time_create']
|
||||||
|
search_fields = [
|
||||||
|
'item__title', 'item__alias'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(models.LibraryItem, LibraryItemAdmin)
|
||||||
|
admin.site.register(models.LibraryTemplate, LibraryTemplateAdmin)
|
||||||
|
admin.site.register(models.Subscription, SubscriptionAdmin)
|
||||||
|
admin.site.register(models.Version, VersionAdmin)
|
||||||
|
admin.site.register(models.Editor, EditorAdmin)
|
8
rsconcept/backend/apps/library/apps.py
Normal file
8
rsconcept/backend/apps/library/apps.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
''' Application: Operation Schema. '''
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class RsformConfig(AppConfig):
|
||||||
|
''' Application config. '''
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'apps.library'
|
92
rsconcept/backend/apps/library/migrations/0001_initial.py
Normal file
92
rsconcept/backend/apps/library/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
# Generated by Django 5.0.7 on 2024-07-25 16:06
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LibraryItem',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('item_type', models.CharField(choices=[('rsform', 'Rsform'), ('oss', 'Operation Schema')], default='rsform', max_length=50, verbose_name='Тип')),
|
||||||
|
('title', models.TextField(verbose_name='Название')),
|
||||||
|
('alias', models.CharField(blank=True, max_length=255, verbose_name='Шифр')),
|
||||||
|
('comment', models.TextField(blank=True, verbose_name='Комментарий')),
|
||||||
|
('visible', models.BooleanField(default=True, verbose_name='Отображаемая')),
|
||||||
|
('read_only', models.BooleanField(default=False, verbose_name='Запретить редактирование')),
|
||||||
|
('access_policy', models.CharField(choices=[('public', 'Public'), ('protected', 'Protected'), ('private', 'Private')], default='public', max_length=500, verbose_name='Политика доступа')),
|
||||||
|
('location', models.TextField(default='/U', max_length=500, verbose_name='Расположение')),
|
||||||
|
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||||
|
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
|
||||||
|
('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Владелец')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Схема',
|
||||||
|
'verbose_name_plural': 'Схемы',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LibraryTemplate',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('lib_source', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='library.libraryitem', verbose_name='Источник')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Шаблон',
|
||||||
|
'verbose_name_plural': 'Шаблоны',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Editor',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата добавления')),
|
||||||
|
('editor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Редактор')),
|
||||||
|
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library.libraryitem', verbose_name='Схема')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Редактор',
|
||||||
|
'verbose_name_plural': 'Редакторы',
|
||||||
|
'unique_together': {('item', 'editor')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Subscription',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library.libraryitem', verbose_name='Элемент')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Подписка',
|
||||||
|
'verbose_name_plural': 'Подписки',
|
||||||
|
'unique_together': {('user', 'item')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Version',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('version', models.CharField(max_length=20, verbose_name='Версия')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Описание')),
|
||||||
|
('data', models.JSONField(verbose_name='Содержание')),
|
||||||
|
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||||
|
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library.libraryitem', verbose_name='Схема')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Версия',
|
||||||
|
'verbose_name_plural': 'Версии',
|
||||||
|
'unique_together': {('item', 'version')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -14,7 +14,7 @@ class Editor(Model):
|
||||||
''' Editor list. '''
|
''' Editor list. '''
|
||||||
item: ForeignKey = ForeignKey(
|
item: ForeignKey = ForeignKey(
|
||||||
verbose_name='Схема',
|
verbose_name='Схема',
|
||||||
to='rsform.LibraryItem',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
)
|
)
|
||||||
editor: ForeignKey = ForeignKey(
|
editor: ForeignKey = ForeignKey(
|
|
@ -54,7 +54,8 @@ class LibraryItem(Model):
|
||||||
item_type: CharField = CharField(
|
item_type: CharField = CharField(
|
||||||
verbose_name='Тип',
|
verbose_name='Тип',
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=LibraryItemType.choices
|
choices=LibraryItemType.choices,
|
||||||
|
default=LibraryItemType.RSFORM
|
||||||
)
|
)
|
||||||
owner: ForeignKey = ForeignKey(
|
owner: ForeignKey = ForeignKey(
|
||||||
verbose_name='Владелец',
|
verbose_name='Владелец',
|
|
@ -6,7 +6,7 @@ class LibraryTemplate(Model):
|
||||||
''' Template for library items and constituents. '''
|
''' Template for library items and constituents. '''
|
||||||
lib_source: ForeignKey = ForeignKey(
|
lib_source: ForeignKey = ForeignKey(
|
||||||
verbose_name='Источник',
|
verbose_name='Источник',
|
||||||
to='rsform.RSForm',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
|
@ -18,7 +18,7 @@ class Subscription(Model):
|
||||||
)
|
)
|
||||||
item: ForeignKey = ForeignKey(
|
item: ForeignKey = ForeignKey(
|
||||||
verbose_name='Элемент',
|
verbose_name='Элемент',
|
||||||
to='rsform.LibraryItem',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Version(Model):
|
||||||
''' Library item version archive. '''
|
''' Library item version archive. '''
|
||||||
item: ForeignKey = ForeignKey(
|
item: ForeignKey = ForeignKey(
|
||||||
verbose_name='Схема',
|
verbose_name='Схема',
|
||||||
to='rsform.LibraryItem',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
)
|
)
|
||||||
version = CharField(
|
version = CharField(
|
7
rsconcept/backend/apps/library/models/__init__.py
Normal file
7
rsconcept/backend/apps/library/models/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
''' Django: Models. '''
|
||||||
|
|
||||||
|
from .Editor import Editor
|
||||||
|
from .LibraryItem import AccessPolicy, LibraryItem, LibraryItemType, LocationHead, validate_location
|
||||||
|
from .LibraryTemplate import LibraryTemplate
|
||||||
|
from .Subscription import Subscription
|
||||||
|
from .Version import Version
|
14
rsconcept/backend/apps/library/serializers/__init__.py
Normal file
14
rsconcept/backend/apps/library/serializers/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
''' REST API: Serializers. '''
|
||||||
|
|
||||||
|
from .basics import AccessPolicySerializer, LocationSerializer
|
||||||
|
from .data_access import (
|
||||||
|
LibraryItemBaseSerializer,
|
||||||
|
LibraryItemCloneSerializer,
|
||||||
|
LibraryItemDetailsSerializer,
|
||||||
|
LibraryItemSerializer,
|
||||||
|
UsersListSerializer,
|
||||||
|
UserTargetSerializer,
|
||||||
|
VersionCreateSerializer,
|
||||||
|
VersionSerializer
|
||||||
|
)
|
||||||
|
from .responses import NewVersionResponse
|
32
rsconcept/backend/apps/library/serializers/basics.py
Normal file
32
rsconcept/backend/apps/library/serializers/basics.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
''' Basic serializers that do not interact with database. '''
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from shared import messages as msg
|
||||||
|
|
||||||
|
from ..models import AccessPolicy, validate_location
|
||||||
|
|
||||||
|
|
||||||
|
class LocationSerializer(serializers.Serializer):
|
||||||
|
''' Serializer: Item location. '''
|
||||||
|
location = serializers.CharField(max_length=500)
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
attrs = super().validate(attrs)
|
||||||
|
if not validate_location(attrs['location']):
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'location': msg.invalidLocation()
|
||||||
|
})
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class AccessPolicySerializer(serializers.Serializer):
|
||||||
|
''' Serializer: Constituenta renaming. '''
|
||||||
|
access_policy = serializers.CharField()
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
attrs = super().validate(attrs)
|
||||||
|
if not attrs['access_policy'] in AccessPolicy.values:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'access_policy': msg.invalidEnum(attrs['access_policy'])
|
||||||
|
})
|
||||||
|
return attrs
|
94
rsconcept/backend/apps/library/serializers/data_access.py
Normal file
94
rsconcept/backend/apps/library/serializers/data_access.py
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
''' Serializers for persistent data manipulation. '''
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
||||||
|
|
||||||
|
from apps.rsform.models import Constituenta
|
||||||
|
|
||||||
|
from ..models import LibraryItem, Version
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryItemBaseSerializer(serializers.ModelSerializer):
|
||||||
|
''' Serializer: LibraryItem entry full access. '''
|
||||||
|
class Meta:
|
||||||
|
''' serializer metadata. '''
|
||||||
|
model = LibraryItem
|
||||||
|
fields = '__all__'
|
||||||
|
read_only_fields = ('id',)
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryItemSerializer(serializers.ModelSerializer):
|
||||||
|
''' Serializer: LibraryItem entry limited access. '''
|
||||||
|
class Meta:
|
||||||
|
''' serializer metadata. '''
|
||||||
|
model = LibraryItem
|
||||||
|
fields = '__all__'
|
||||||
|
read_only_fields = ('id', 'item_type', 'owner', 'location', 'access_policy')
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryItemCloneSerializer(serializers.ModelSerializer):
|
||||||
|
''' Serializer: LibraryItem cloning. '''
|
||||||
|
items = PKField(many=True, required=False, queryset=Constituenta.objects.all())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
''' serializer metadata. '''
|
||||||
|
model = LibraryItem
|
||||||
|
exclude = ['id', 'item_type', 'owner']
|
||||||
|
|
||||||
|
|
||||||
|
class VersionSerializer(serializers.ModelSerializer):
|
||||||
|
''' Serializer: Version data. '''
|
||||||
|
class Meta:
|
||||||
|
''' serializer metadata. '''
|
||||||
|
model = Version
|
||||||
|
fields = 'id', 'version', 'item', 'description', 'time_create'
|
||||||
|
read_only_fields = ('id', 'item', 'time_create')
|
||||||
|
|
||||||
|
|
||||||
|
class VersionInnerSerializer(serializers.ModelSerializer):
|
||||||
|
''' Serializer: Version data for list of versions. '''
|
||||||
|
class Meta:
|
||||||
|
''' serializer metadata. '''
|
||||||
|
model = Version
|
||||||
|
fields = 'id', 'version', 'description', 'time_create'
|
||||||
|
read_only_fields = ('id', 'item', 'time_create')
|
||||||
|
|
||||||
|
|
||||||
|
class VersionCreateSerializer(serializers.ModelSerializer):
|
||||||
|
''' Serializer: Version create data. '''
|
||||||
|
class Meta:
|
||||||
|
''' serializer metadata. '''
|
||||||
|
model = Version
|
||||||
|
fields = 'version', 'description'
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||||
|
''' Serializer: LibraryItem detailed data. '''
|
||||||
|
subscribers = serializers.SerializerMethodField()
|
||||||
|
editors = serializers.SerializerMethodField()
|
||||||
|
versions = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
''' serializer metadata. '''
|
||||||
|
model = LibraryItem
|
||||||
|
fields = '__all__'
|
||||||
|
read_only_fields = ('owner', 'id', 'item_type')
|
||||||
|
|
||||||
|
def get_subscribers(self, instance: LibraryItem) -> list[int]:
|
||||||
|
return [item.pk for item in instance.subscribers()]
|
||||||
|
|
||||||
|
def get_editors(self, instance: LibraryItem) -> list[int]:
|
||||||
|
return [item.pk for item in instance.editors()]
|
||||||
|
|
||||||
|
def get_versions(self, instance: LibraryItem) -> list:
|
||||||
|
return [VersionInnerSerializer(item).data for item in instance.versions()]
|
||||||
|
|
||||||
|
|
||||||
|
class UserTargetSerializer(serializers.Serializer):
|
||||||
|
''' Serializer: Target single User. '''
|
||||||
|
user = PKField(many=False, queryset=User.objects.all())
|
||||||
|
|
||||||
|
|
||||||
|
class UsersListSerializer(serializers.Serializer):
|
||||||
|
''' Serializer: List of Users. '''
|
||||||
|
users = PKField(many=True, queryset=User.objects.all())
|
8
rsconcept/backend/apps/library/serializers/responses.py
Normal file
8
rsconcept/backend/apps/library/serializers/responses.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class NewVersionResponse(serializers.Serializer):
|
||||||
|
''' Serializer: Create version response. '''
|
||||||
|
version = serializers.IntegerField()
|
||||||
|
schema = serializers.JSONField()
|
3
rsconcept/backend/apps/library/tests/__init__.py
Normal file
3
rsconcept/backend/apps/library/tests/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
''' Tests. '''
|
||||||
|
from .s_models import *
|
||||||
|
from .s_views import *
|
|
@ -0,0 +1,4 @@
|
||||||
|
''' Tests for Django Models. '''
|
||||||
|
from .t_Editor import *
|
||||||
|
from .t_LibraryItem import *
|
||||||
|
from .t_Subscription import *
|
|
@ -1,7 +1,8 @@
|
||||||
''' Testing models: Editor. '''
|
''' Testing models: Editor. '''
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from apps.rsform.models import Editor, LibraryItemType, RSForm, User
|
from apps.library.models import Editor, LibraryItem, LibraryItemType
|
||||||
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
class TestEditor(TestCase):
|
class TestEditor(TestCase):
|
||||||
|
@ -10,7 +11,8 @@ class TestEditor(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user1 = User.objects.create(username='User1')
|
self.user1 = User.objects.create(username='User1')
|
||||||
self.user2 = User.objects.create(username='User2')
|
self.user2 = User.objects.create(username='User2')
|
||||||
self.item = RSForm.objects.create(
|
self.item = LibraryItem.objects.create(
|
||||||
|
item_type=LibraryItemType.RSFORM,
|
||||||
title='Test',
|
title='Test',
|
||||||
alias='КС1',
|
alias='КС1',
|
||||||
owner=self.user1
|
owner=self.user1
|
|
@ -1,15 +1,15 @@
|
||||||
''' Testing models: LibraryItem. '''
|
''' Testing models: LibraryItem. '''
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from apps.rsform.models import (
|
from apps.library.models import (
|
||||||
AccessPolicy,
|
AccessPolicy,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
LibraryItemType,
|
LibraryItemType,
|
||||||
LocationHead,
|
LocationHead,
|
||||||
Subscription,
|
Subscription,
|
||||||
User,
|
|
||||||
validate_location
|
validate_location
|
||||||
)
|
)
|
||||||
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
class TestLibraryItem(TestCase):
|
class TestLibraryItem(TestCase):
|
|
@ -1,7 +1,8 @@
|
||||||
''' Testing models: Subscription. '''
|
''' Testing models: Subscription. '''
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from apps.rsform.models import LibraryItem, LibraryItemType, Subscription, User
|
from apps.library.models import LibraryItem, LibraryItemType, Subscription
|
||||||
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
class TestSubscription(TestCase):
|
class TestSubscription(TestCase):
|
3
rsconcept/backend/apps/library/tests/s_views/__init__.py
Normal file
3
rsconcept/backend/apps/library/tests/s_views/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
''' Tests for REST API. '''
|
||||||
|
from .t_library import *
|
||||||
|
from .t_versions import *
|
|
@ -1,16 +1,16 @@
|
||||||
''' Testing API: Library. '''
|
''' Testing API: Library. '''
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from apps.rsform.models import (
|
from apps.library.models import (
|
||||||
AccessPolicy,
|
AccessPolicy,
|
||||||
Editor,
|
Editor,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
LibraryItemType,
|
LibraryItemType,
|
||||||
LibraryTemplate,
|
LibraryTemplate,
|
||||||
LocationHead,
|
LocationHead,
|
||||||
RSForm,
|
|
||||||
Subscription
|
Subscription
|
||||||
)
|
)
|
||||||
|
from apps.rsform.models import RSForm
|
||||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||||
from shared.testing_utils import response_contains
|
from shared.testing_utils import response_contains
|
||||||
|
|
||||||
|
@ -20,16 +20,16 @@ class TestLibraryViewset(EndpointTester):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.owned = RSForm.objects.create(
|
self.owned = LibraryItem.objects.create(
|
||||||
title='Test',
|
title='Test',
|
||||||
alias='T1',
|
alias='T1',
|
||||||
owner=self.user
|
owner=self.user
|
||||||
)
|
)
|
||||||
self.unowned = RSForm.objects.create(
|
self.unowned = LibraryItem.objects.create(
|
||||||
title='Test2',
|
title='Test2',
|
||||||
alias='T2'
|
alias='T2'
|
||||||
)
|
)
|
||||||
self.common = RSForm.objects.create(
|
self.common = LibraryItem.objects.create(
|
||||||
title='Test3',
|
title='Test3',
|
||||||
alias='T3',
|
alias='T3',
|
||||||
location=LocationHead.COMMON
|
location=LocationHead.COMMON
|
||||||
|
@ -44,12 +44,16 @@ class TestLibraryViewset(EndpointTester):
|
||||||
'title': 'Title',
|
'title': 'Title',
|
||||||
'alias': 'alias',
|
'alias': 'alias',
|
||||||
}
|
}
|
||||||
self.executeBadData(data=data)
|
response = self.executeCreated(data=data)
|
||||||
|
self.assertEqual(response.data['owner'], self.user.pk)
|
||||||
|
self.assertEqual(response.data['item_type'], LibraryItemType.RSFORM)
|
||||||
|
self.assertEqual(response.data['title'], data['title'])
|
||||||
|
self.assertEqual(response.data['alias'], data['alias'])
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'item_type': LibraryItemType.OPERATION_SCHEMA,
|
'item_type': LibraryItemType.OPERATION_SCHEMA,
|
||||||
'title': 'Title',
|
'title': 'Title2',
|
||||||
'alias': 'alias',
|
'alias': 'alias2',
|
||||||
'access_policy': AccessPolicy.PROTECTED,
|
'access_policy': AccessPolicy.PROTECTED,
|
||||||
'visible': False,
|
'visible': False,
|
||||||
'read_only': True
|
'read_only': True
|
||||||
|
@ -359,12 +363,13 @@ class TestLibraryViewset(EndpointTester):
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/clone', method='post')
|
@decl_endpoint('/api/library/{item}/clone', method='post')
|
||||||
def test_clone_rsform(self):
|
def test_clone_rsform(self):
|
||||||
x12 = self.owned.insert_new(
|
schema = RSForm(self.owned)
|
||||||
|
x12 = schema.insert_new(
|
||||||
alias='X12',
|
alias='X12',
|
||||||
term_raw='человек',
|
term_raw='человек',
|
||||||
term_resolved='человек'
|
term_resolved='человек'
|
||||||
)
|
)
|
||||||
d2 = self.owned.insert_new(
|
d2 = schema.insert_new(
|
||||||
alias='D2',
|
alias='D2',
|
||||||
term_raw='@{X12|plur}',
|
term_raw='@{X12|plur}',
|
||||||
term_resolved='люди'
|
term_resolved='люди'
|
|
@ -15,51 +15,72 @@ class TestVersionViews(EndpointTester):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.owned = RSForm.objects.create(title='Test', alias='T1', owner=self.user)
|
self.owned = RSForm.create(title='Test', alias='T1', owner=self.user)
|
||||||
self.unowned = RSForm.objects.create(title='Test2', alias='T2')
|
self.owned_id = self.owned.model.pk
|
||||||
|
self.unowned = RSForm.create(title='Test2', alias='T2')
|
||||||
|
self.unowned_id = self.unowned.model.pk
|
||||||
self.x1 = self.owned.insert_new(
|
self.x1 = self.owned.insert_new(
|
||||||
alias='X1',
|
alias='X1',
|
||||||
convention='testStart'
|
convention='testStart'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{schema}/versions/create', method='post')
|
@decl_endpoint('/api/library/{schema}/create-version', method='post')
|
||||||
def test_create_version(self):
|
def test_create_version(self):
|
||||||
invalid_data = {'description': 'test'}
|
invalid_data = {'description': 'test'}
|
||||||
invalid_id = 1338
|
invalid_id = 1338
|
||||||
data = {'version': '1.0.0', 'description': 'test'}
|
data = {'version': '1.0.0', 'description': 'test'}
|
||||||
|
|
||||||
self.executeNotFound(data=data, schema=invalid_id)
|
self.executeNotFound(data=data, schema=invalid_id)
|
||||||
self.executeForbidden(data=data, schema=self.unowned.pk)
|
self.executeForbidden(data=data, schema=self.unowned_id)
|
||||||
self.executeBadData(data=invalid_data, schema=self.owned.pk)
|
self.executeBadData(data=invalid_data, schema=self.owned_id)
|
||||||
|
|
||||||
response = self.executeCreated(data=data, schema=self.owned.pk)
|
response = self.executeCreated(data=data, schema=self.owned_id)
|
||||||
self.assertTrue('version' in response.data)
|
self.assertTrue('version' in response.data)
|
||||||
self.assertTrue('schema' in response.data)
|
self.assertTrue('schema' in response.data)
|
||||||
self.assertTrue(response.data['version'] in [v['id'] for v in response.data['schema']['versions']])
|
self.assertTrue(response.data['version'] in [v['id'] for v in response.data['schema']['versions']])
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{schema}/versions/{version}', method='get')
|
@decl_endpoint('/api/library/{schema}/versions/{version}', method='get')
|
||||||
def test_retrieve_version(self):
|
def test_retrieve_version(self):
|
||||||
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
||||||
invalid_id = version_id + 1337
|
invalid_id = version_id + 1337
|
||||||
|
|
||||||
self.executeNotFound(schema=invalid_id, version=invalid_id)
|
self.executeNotFound(schema=invalid_id, version=invalid_id)
|
||||||
self.executeNotFound(schema=self.owned.pk, version=invalid_id)
|
self.executeNotFound(schema=self.owned_id, version=invalid_id)
|
||||||
self.executeNotFound(schema=invalid_id, version=version_id)
|
self.executeNotFound(schema=invalid_id, version=version_id)
|
||||||
self.executeNotFound(schema=self.unowned.pk, version=version_id)
|
self.executeNotFound(schema=self.unowned_id, version=version_id)
|
||||||
|
|
||||||
self.owned.alias = 'NewName'
|
self.owned.model.alias = 'NewName'
|
||||||
self.owned.save()
|
self.owned.save()
|
||||||
self.x1.alias = 'X33'
|
self.x1.alias = 'X33'
|
||||||
self.x1.save()
|
self.x1.save()
|
||||||
|
|
||||||
response = self.executeOK(schema=self.owned.pk, version=version_id)
|
response = self.executeOK(schema=self.owned_id, version=version_id)
|
||||||
self.assertNotEqual(response.data['alias'], self.owned.alias)
|
self.assertNotEqual(response.data['alias'], self.owned.model.alias)
|
||||||
self.assertNotEqual(response.data['items'][0]['alias'], self.x1.alias)
|
self.assertNotEqual(response.data['items'][0]['alias'], self.x1.alias)
|
||||||
self.assertEqual(response.data['version'], version_id)
|
self.assertEqual(response.data['version'], version_id)
|
||||||
|
|
||||||
|
|
||||||
|
@decl_endpoint('/api/library/{schema}/versions/{version}', method='get')
|
||||||
|
def test_retrieve_version_details(self):
|
||||||
|
a1 = Constituenta.objects.create(
|
||||||
|
schema=self.owned.model,
|
||||||
|
alias='A1',
|
||||||
|
cst_type='axiom',
|
||||||
|
definition_formal='X1=X1',
|
||||||
|
order=2
|
||||||
|
)
|
||||||
|
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
||||||
|
a1.definition_formal = 'X1=X2'
|
||||||
|
a1.save()
|
||||||
|
|
||||||
|
response = self.executeOK(schema=self.owned_id, version=version_id)
|
||||||
|
loaded_a1 = response.data['items'][1]
|
||||||
|
self.assertEqual(loaded_a1['definition_formal'], 'X1=X1')
|
||||||
|
self.assertEqual(loaded_a1['parse']['status'], 'verified')
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/versions/{version}', method='get')
|
@decl_endpoint('/api/versions/{version}', method='get')
|
||||||
def test_access_version(self):
|
def test_access_version(self):
|
||||||
data = {'version': '1.0.0', 'description': 'test'}
|
data = {'version': '1.0.0', 'description': 'test'}
|
||||||
|
@ -73,7 +94,7 @@ class TestVersionViews(EndpointTester):
|
||||||
response = self.executeOK()
|
response = self.executeOK()
|
||||||
self.assertEqual(response.data['version'], data['version'])
|
self.assertEqual(response.data['version'], data['version'])
|
||||||
self.assertEqual(response.data['description'], data['description'])
|
self.assertEqual(response.data['description'], data['description'])
|
||||||
self.assertEqual(response.data['item'], self.owned.pk)
|
self.assertEqual(response.data['item'], self.owned_id)
|
||||||
|
|
||||||
data = {'version': '1.2.0', 'description': 'test1'}
|
data = {'version': '1.2.0', 'description': 'test1'}
|
||||||
self.method = 'patch'
|
self.method = 'patch'
|
||||||
|
@ -95,25 +116,6 @@ class TestVersionViews(EndpointTester):
|
||||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{schema}/versions/{version}', method='get')
|
|
||||||
def test_retrieve_version_details(self):
|
|
||||||
a1 = Constituenta.objects.create(
|
|
||||||
schema=self.owned,
|
|
||||||
alias='A1',
|
|
||||||
cst_type='axiom',
|
|
||||||
definition_formal='X1=X1',
|
|
||||||
order=2
|
|
||||||
)
|
|
||||||
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
|
||||||
a1.definition_formal = 'X1=X2'
|
|
||||||
a1.save()
|
|
||||||
|
|
||||||
response = self.executeOK(schema=self.owned.pk, version=version_id)
|
|
||||||
loaded_a1 = response.data['items'][1]
|
|
||||||
self.assertEqual(loaded_a1['definition_formal'], 'X1=X1')
|
|
||||||
self.assertEqual(loaded_a1['parse']['status'], 'verified')
|
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/versions/{version}/export-file', method='get')
|
@decl_endpoint('/api/versions/{version}/export-file', method='get')
|
||||||
def test_export_version(self):
|
def test_export_version(self):
|
||||||
invalid_id = 1338
|
invalid_id = 1338
|
||||||
|
@ -123,7 +125,7 @@ class TestVersionViews(EndpointTester):
|
||||||
response = self.executeOK(version=version_id)
|
response = self.executeOK(version=version_id)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response.headers['Content-Disposition'],
|
response.headers['Content-Disposition'],
|
||||||
f'attachment; filename={self.owned.alias}.trs'
|
f'attachment; filename={self.owned.model.alias}.trs'
|
||||||
)
|
)
|
||||||
with io.BytesIO(response.content) as stream:
|
with io.BytesIO(response.content) as stream:
|
||||||
with ZipFile(stream, 'r') as zipped_file:
|
with ZipFile(stream, 'r') as zipped_file:
|
||||||
|
@ -165,7 +167,7 @@ class TestVersionViews(EndpointTester):
|
||||||
|
|
||||||
def _create_version(self, data) -> int:
|
def _create_version(self, data) -> int:
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
f'/api/rsforms/{self.owned.pk}/versions/create',
|
f'/api/library/{self.owned_id}/create-version',
|
||||||
data=data, format='json'
|
data=data, format='json'
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
21
rsconcept/backend/apps/library/urls.py
Normal file
21
rsconcept/backend/apps/library/urls.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
''' Routing: Operation Schema. '''
|
||||||
|
from django.urls import include, path
|
||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
library_router = routers.SimpleRouter(trailing_slash=False)
|
||||||
|
library_router.register('library', views.LibraryViewSet, 'Library')
|
||||||
|
library_router.register('versions', views.VersionViewset, 'Version')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('library/active', views.LibraryActiveView.as_view()),
|
||||||
|
path('library/all', views.LibraryAdminView.as_view()),
|
||||||
|
path('library/templates', views.LibraryTemplatesView.as_view(), name='templates'),
|
||||||
|
path('library/<int:pk_item>/create-version', views.create_version),
|
||||||
|
path('library/<int:pk_item>/versions/<int:pk_version>', views.retrieve_version),
|
||||||
|
|
||||||
|
path('versions/<int:pk>/export-file', views.export_file),
|
||||||
|
|
||||||
|
path('', include(library_router.urls)),
|
||||||
|
]
|
3
rsconcept/backend/apps/library/views/__init__.py
Normal file
3
rsconcept/backend/apps/library/views/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
''' REST API: Endpoint processors. '''
|
||||||
|
from .library import LibraryActiveView, LibraryAdminView, LibraryTemplatesView, LibraryViewSet
|
||||||
|
from .versions import VersionViewset, create_version, export_file, retrieve_version
|
|
@ -13,6 +13,9 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from apps.rsform.models import RSForm
|
||||||
|
from apps.rsform.serializers import RSFormParseSerializer
|
||||||
|
from apps.users.models import User
|
||||||
from shared import permissions
|
from shared import permissions
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
|
@ -73,7 +76,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
tags=['Library'],
|
tags=['Library'],
|
||||||
request=s.LibraryItemCloneSerializer,
|
request=s.LibraryItemCloneSerializer,
|
||||||
responses={
|
responses={
|
||||||
c.HTTP_201_CREATED: s.RSFormParseSerializer,
|
c.HTTP_201_CREATED: RSFormParseSerializer,
|
||||||
c.HTTP_400_BAD_REQUEST: None,
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
@ -88,8 +91,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
if item.item_type != m.LibraryItemType.RSFORM:
|
if item.item_type != m.LibraryItemType.RSFORM:
|
||||||
return Response(status=c.HTTP_400_BAD_REQUEST)
|
return Response(status=c.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
schema = m.RSForm.objects.get(pk=item.pk)
|
clone = deepcopy(item)
|
||||||
clone = deepcopy(schema)
|
|
||||||
clone.pk = None
|
clone.pk = None
|
||||||
clone.owner = self.request.user
|
clone.owner = self.request.user
|
||||||
clone.title = serializer.validated_data['title']
|
clone.title = serializer.validated_data['title']
|
||||||
|
@ -103,14 +105,14 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
clone.save()
|
clone.save()
|
||||||
need_filter = 'items' in request.data
|
need_filter = 'items' in request.data
|
||||||
for cst in schema.constituents():
|
for cst in RSForm(item).constituents():
|
||||||
if not need_filter or cst.pk in request.data['items']:
|
if not need_filter or cst.pk in request.data['items']:
|
||||||
cst.pk = None
|
cst.pk = None
|
||||||
cst.schema = clone
|
cst.schema = clone
|
||||||
cst.save()
|
cst.save()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
data=s.RSFormParseSerializer(clone).data
|
data=RSFormParseSerializer(clone).data
|
||||||
)
|
)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -127,7 +129,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
def subscribe(self, request: Request, pk):
|
def subscribe(self, request: Request, pk):
|
||||||
''' Endpoint: Subscribe current user to item. '''
|
''' Endpoint: Subscribe current user to item. '''
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
m.Subscription.subscribe(user=cast(m.User, self.request.user), item=item)
|
m.Subscription.subscribe(user=cast(User, self.request.user), item=item)
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -144,7 +146,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
def unsubscribe(self, request: Request, pk):
|
def unsubscribe(self, request: Request, pk):
|
||||||
''' Endpoint: Unsubscribe current user from item. '''
|
''' Endpoint: Unsubscribe current user from item. '''
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
m.Subscription.unsubscribe(user=cast(m.User, self.request.user), item=item)
|
m.Subscription.unsubscribe(user=cast(User, self.request.user), item=item)
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -184,7 +186,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
serializer = s.AccessPolicySerializer(data=request.data)
|
serializer = s.AccessPolicySerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
m.LibraryItem.objects.filter(pk=item.pk).update(access_policy=serializer.validated_data['access_policy'])
|
new_policy = serializer.validated_data['access_policy']
|
||||||
|
m.LibraryItem.objects.filter(pk=item.pk).update(access_policy=new_policy)
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -286,7 +289,7 @@ class LibraryActiveView(generics.ListAPIView):
|
||||||
.filter(is_public) \
|
.filter(is_public) \
|
||||||
.filter(common_location).order_by('-time_update')
|
.filter(common_location).order_by('-time_update')
|
||||||
else:
|
else:
|
||||||
user = cast(m.User, self.request.user)
|
user = cast(User, self.request.user)
|
||||||
# pylint: disable=unsupported-binary-operation
|
# pylint: disable=unsupported-binary-operation
|
||||||
return m.LibraryItem.objects.filter(
|
return m.LibraryItem.objects.filter(
|
||||||
(is_public & common_location) |
|
(is_public & common_location) |
|
|
@ -10,11 +10,13 @@ from rest_framework.decorators import action, api_view, permission_classes
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from shared import permissions
|
from apps.rsform import utils
|
||||||
|
from apps.rsform.models import RSForm
|
||||||
|
from apps.rsform.serializers import RSFormParseSerializer, RSFormSerializer, RSFormTRSSerializer
|
||||||
|
from shared import permissions, utility
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
from .. import serializers as s
|
from .. import serializers as s
|
||||||
from .. import utils
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=['Version'])
|
@extend_schema(tags=['Version'])
|
||||||
|
@ -32,7 +34,7 @@ class VersionViewset(
|
||||||
summary='restore version data into current item',
|
summary='restore version data into current item',
|
||||||
request=None,
|
request=None,
|
||||||
responses={
|
responses={
|
||||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
c.HTTP_200_OK: RSFormParseSerializer,
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
|
@ -42,81 +44,10 @@ class VersionViewset(
|
||||||
''' 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 = cast(m.LibraryItem, version.item)
|
||||||
schema = m.RSForm.objects.get(pk=item.pk)
|
RSFormSerializer(item).restore_from_version(version.data)
|
||||||
s.RSFormSerializer(schema).restore_from_version(version.data)
|
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.RSFormParseSerializer(schema).data
|
data=RSFormParseSerializer(item).data
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
summary='save version for RSForm copying current content',
|
|
||||||
tags=['Version'],
|
|
||||||
request=s.VersionCreateSerializer,
|
|
||||||
responses={
|
|
||||||
c.HTTP_201_CREATED: s.NewVersionResponse,
|
|
||||||
c.HTTP_400_BAD_REQUEST: None,
|
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
|
||||||
c.HTTP_404_NOT_FOUND: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@api_view(['POST'])
|
|
||||||
@permission_classes([permissions.GlobalUser])
|
|
||||||
def create_version(request: Request, pk_item: int):
|
|
||||||
''' Endpoint: Create new version for RSForm copying current content. '''
|
|
||||||
try:
|
|
||||||
item = m.RSForm.objects.get(pk=pk_item)
|
|
||||||
except m.LibraryItem.DoesNotExist:
|
|
||||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
|
||||||
creator = request.user
|
|
||||||
if not creator.is_staff and creator != item.owner:
|
|
||||||
return Response(status=c.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
version_input = s.VersionCreateSerializer(data=request.data)
|
|
||||||
version_input.is_valid(raise_exception=True)
|
|
||||||
data = s.RSFormSerializer(item).to_versioned_data()
|
|
||||||
result = item.create_version(
|
|
||||||
version=version_input.validated_data['version'],
|
|
||||||
description=version_input.validated_data['description'],
|
|
||||||
data=data
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
status=c.HTTP_201_CREATED,
|
|
||||||
data={
|
|
||||||
'version': result.pk,
|
|
||||||
'schema': s.RSFormParseSerializer(item).data
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
summary='retrieve versioned data for RSForm',
|
|
||||||
tags=['Version'],
|
|
||||||
request=None,
|
|
||||||
responses={
|
|
||||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
|
||||||
c.HTTP_404_NOT_FOUND: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@api_view(['GET'])
|
|
||||||
def retrieve_version(request: Request, pk_item: int, pk_version: int):
|
|
||||||
''' Endpoint: Retrieve version for RSForm. '''
|
|
||||||
try:
|
|
||||||
item = m.RSForm.objects.get(pk=pk_item)
|
|
||||||
except m.RSForm.DoesNotExist:
|
|
||||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
|
||||||
try:
|
|
||||||
version = m.Version.objects.get(pk=pk_version)
|
|
||||||
except m.Version.DoesNotExist:
|
|
||||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
|
||||||
if version.item != item:
|
|
||||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
data = s.RSFormParseSerializer(item).from_versioned_data(version.pk, version.data)
|
|
||||||
return Response(
|
|
||||||
status=c.HTTP_200_OK,
|
|
||||||
data=data
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -136,10 +67,79 @@ def export_file(request: Request, pk: int):
|
||||||
version = m.Version.objects.get(pk=pk)
|
version = m.Version.objects.get(pk=pk)
|
||||||
except m.Version.DoesNotExist:
|
except m.Version.DoesNotExist:
|
||||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||||
schema = m.RSForm.objects.get(pk=version.item.pk)
|
data = RSFormTRSSerializer(version.item).from_versioned_data(version.data)
|
||||||
data = s.RSFormTRSSerializer(schema).from_versioned_data(version.data)
|
file = utility.write_zipped_json(data, utils.EXTEOR_INNER_FILENAME)
|
||||||
file = utils.write_zipped_json(data, utils.EXTEOR_INNER_FILENAME)
|
|
||||||
filename = utils.filename_for_schema(data['alias'])
|
filename = utils.filename_for_schema(data['alias'])
|
||||||
response = HttpResponse(file, content_type='application/zip')
|
response = HttpResponse(file, content_type='application/zip')
|
||||||
response['Content-Disposition'] = f'attachment; filename={filename}'
|
response['Content-Disposition'] = f'attachment; filename={filename}'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='save version for RSForm copying current content',
|
||||||
|
tags=['Version'],
|
||||||
|
request=s.VersionCreateSerializer,
|
||||||
|
responses={
|
||||||
|
c.HTTP_201_CREATED: s.NewVersionResponse,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([permissions.GlobalUser])
|
||||||
|
def create_version(request: Request, pk_item: int):
|
||||||
|
''' Endpoint: Create new version for RSForm copying current content. '''
|
||||||
|
try:
|
||||||
|
item = m.LibraryItem.objects.get(pk=pk_item)
|
||||||
|
except m.LibraryItem.DoesNotExist:
|
||||||
|
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||||
|
creator = request.user
|
||||||
|
if not creator.is_staff and creator != item.owner:
|
||||||
|
return Response(status=c.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
version_input = s.VersionCreateSerializer(data=request.data)
|
||||||
|
version_input.is_valid(raise_exception=True)
|
||||||
|
data = RSFormSerializer(item).to_versioned_data()
|
||||||
|
result = RSForm(item).create_version(
|
||||||
|
version=version_input.validated_data['version'],
|
||||||
|
description=version_input.validated_data['description'],
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_201_CREATED,
|
||||||
|
data={
|
||||||
|
'version': result.pk,
|
||||||
|
'schema': RSFormParseSerializer(item).data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='retrieve versioned data for RSForm',
|
||||||
|
tags=['Version'],
|
||||||
|
request=None,
|
||||||
|
responses={
|
||||||
|
c.HTTP_200_OK: RSFormParseSerializer,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@api_view(['GET'])
|
||||||
|
def retrieve_version(request: Request, pk_item: int, pk_version: int):
|
||||||
|
''' Endpoint: Retrieve version for RSForm. '''
|
||||||
|
try:
|
||||||
|
item = m.LibraryItem.objects.get(pk=pk_item)
|
||||||
|
except m.LibraryItem.DoesNotExist:
|
||||||
|
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||||
|
try:
|
||||||
|
version = m.Version.objects.get(pk=pk_version)
|
||||||
|
except m.Version.DoesNotExist:
|
||||||
|
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||||
|
if version.item != item:
|
||||||
|
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
data = RSFormParseSerializer(item).from_versioned_data(version.pk, version.data)
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=data
|
||||||
|
)
|
|
@ -27,4 +27,4 @@ class SynthesisSubstitutionAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
admin.site.register(models.Operation, OperationAdmin)
|
admin.site.register(models.Operation, OperationAdmin)
|
||||||
admin.site.register(models.Argument, ArgumentAdmin)
|
admin.site.register(models.Argument, ArgumentAdmin)
|
||||||
admin.site.register(models.SynthesisSubstitution, SynthesisSubstitutionAdmin)
|
admin.site.register(models.Substitution, SynthesisSubstitutionAdmin)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class RsformConfig(AppConfig):
|
class OssConfig(AppConfig):
|
||||||
''' Application config. '''
|
''' Application config. '''
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'apps.oss'
|
name = 'apps.oss'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.0.7 on 2024-07-17 09:51
|
# Generated by Django 5.0.7 on 2024-07-25 16:06
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -9,7 +9,8 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('rsform', '0008_alter_libraryitem_item_type'),
|
('library', '0001_initial'),
|
||||||
|
('rsform', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -17,17 +18,15 @@ class Migration(migrations.Migration):
|
||||||
name='Operation',
|
name='Operation',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('operation_type', models.CharField(choices=[
|
('operation_type', models.CharField(choices=[('input', 'Input'), ('synthesis', 'Synthesis')], default='input', max_length=10, verbose_name='Тип')),
|
||||||
('input', 'Input'), ('synthesis', 'Synthesis')], default='input', max_length=10, verbose_name='Тип')),
|
('sync_text', models.BooleanField(default=True, verbose_name='Синхронизация')),
|
||||||
('alias', models.CharField(blank=True, max_length=255, verbose_name='Шифр')),
|
('alias', models.CharField(blank=True, max_length=255, verbose_name='Шифр')),
|
||||||
('title', models.TextField(blank=True, verbose_name='Название')),
|
('title', models.TextField(blank=True, verbose_name='Название')),
|
||||||
('comment', models.TextField(blank=True, verbose_name='Комментарий')),
|
('comment', models.TextField(blank=True, verbose_name='Комментарий')),
|
||||||
('position_x', models.FloatField(default=0, verbose_name='Положение по горизонтали')),
|
('position_x', models.FloatField(default=0, verbose_name='Положение по горизонтали')),
|
||||||
('position_y', models.FloatField(default=0, verbose_name='Положение по вертикали')),
|
('position_y', models.FloatField(default=0, verbose_name='Положение по вертикали')),
|
||||||
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='library.libraryitem', verbose_name='Схема синтеза')),
|
||||||
related_name='items', to='rsform.libraryitem', verbose_name='Схема синтеза')),
|
('result', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='producer', to='library.libraryitem', verbose_name='Связанная КС')),
|
||||||
('result', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name='producer', to='rsform.libraryitem', verbose_name='Связанная КС')),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Операция',
|
'verbose_name': 'Операция',
|
||||||
|
@ -35,16 +34,13 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SynthesisSubstitution',
|
name='Substitution',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('transfer_term', models.BooleanField(default=False, verbose_name='Перенос термина')),
|
('transfer_term', models.BooleanField(default=False, verbose_name='Перенос термина')),
|
||||||
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oss.operation', verbose_name='Операция')),
|
||||||
to='oss.operation', verbose_name='Операция')),
|
('original', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='as_original', to='rsform.constituenta', verbose_name='Удаляемая конституента')),
|
||||||
('original', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
('substitution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='as_substitute', to='rsform.constituenta', verbose_name='Замещающая конституента')),
|
||||||
related_name='as_original', to='rsform.constituenta', verbose_name='Удаляемая конституента')),
|
|
||||||
('substitution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name='as_substitute', to='rsform.constituenta', verbose_name='Замещающая конституента')),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Отождествление синтеза',
|
'verbose_name': 'Отождествление синтеза',
|
||||||
|
@ -55,10 +51,8 @@ class Migration(migrations.Migration):
|
||||||
name='Argument',
|
name='Argument',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('argument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
('argument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='descendants', to='oss.operation', verbose_name='Аргумент')),
|
||||||
related_name='descendants', to='oss.operation', verbose_name='Аргумент')),
|
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arguments', to='oss.operation', verbose_name='Операция')),
|
||||||
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name='arguments', to='oss.operation', verbose_name='Операция')),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Аргумент',
|
'verbose_name': 'Аргумент',
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
# Generated by Django 5.0.7 on 2024-07-22 13:23
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('oss', '0001_initial'),
|
|
||||||
('rsform', '0008_alter_libraryitem_item_type'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='OperationSchema',
|
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'proxy': True,
|
|
||||||
'indexes': [],
|
|
||||||
'constraints': [],
|
|
||||||
},
|
|
||||||
bases=('rsform.libraryitem',),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='operation',
|
|
||||||
name='oss',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='oss.operationschema', verbose_name='Схема синтеза'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Generated by Django 5.0.7 on 2024-07-24 18:12
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('oss', '0002_operationschema_alter_operation_oss'),
|
|
||||||
('rsform', '0009_rsform_alter_constituenta_schema_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='operation',
|
|
||||||
name='sync_text',
|
|
||||||
field=models.BooleanField(default=True, verbose_name='Синхронизация'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='operation',
|
|
||||||
name='result',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='producer', to='rsform.rsform', verbose_name='Связанная КС'),
|
|
||||||
),
|
|
||||||
]
|
|
32
rsconcept/backend/apps/oss/models/Inheritance.py
Normal file
32
rsconcept/backend/apps/oss/models/Inheritance.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
''' Models: Synthesis Inheritance. '''
|
||||||
|
from django.db.models import CASCADE, ForeignKey, Model
|
||||||
|
|
||||||
|
|
||||||
|
class Inheritance(Model):
|
||||||
|
''' Inheritance links parent and child constituents in synthesis operation.'''
|
||||||
|
operation: ForeignKey = ForeignKey(
|
||||||
|
verbose_name='Операция',
|
||||||
|
to='oss.Operation',
|
||||||
|
on_delete=CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
parent: ForeignKey = ForeignKey(
|
||||||
|
verbose_name='Исходная конституента',
|
||||||
|
to='rsform.Constituenta',
|
||||||
|
on_delete=CASCADE,
|
||||||
|
related_name='as_parent'
|
||||||
|
)
|
||||||
|
child: ForeignKey = ForeignKey(
|
||||||
|
verbose_name='Наследованная конституента',
|
||||||
|
to='rsform.Constituenta',
|
||||||
|
on_delete=CASCADE,
|
||||||
|
related_name='as_child'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
''' Model metadata. '''
|
||||||
|
verbose_name = 'Наследование синтеза'
|
||||||
|
verbose_name_plural = 'Отношение наследования конституент'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'{self.parent} -> {self.child}'
|
|
@ -22,7 +22,7 @@ class Operation(Model):
|
||||||
''' Operational schema Unit.'''
|
''' Operational schema Unit.'''
|
||||||
oss: ForeignKey = ForeignKey(
|
oss: ForeignKey = ForeignKey(
|
||||||
verbose_name='Схема синтеза',
|
verbose_name='Схема синтеза',
|
||||||
to='oss.OperationSchema',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
related_name='items'
|
related_name='items'
|
||||||
)
|
)
|
||||||
|
@ -34,7 +34,7 @@ class Operation(Model):
|
||||||
)
|
)
|
||||||
result: ForeignKey = ForeignKey(
|
result: ForeignKey = ForeignKey(
|
||||||
verbose_name='Связанная КС',
|
verbose_name='Связанная КС',
|
||||||
to='rsform.RSForm',
|
to='library.LibraryItem',
|
||||||
null=True,
|
null=True,
|
||||||
on_delete=SET_NULL,
|
on_delete=SET_NULL,
|
||||||
related_name='producer'
|
related_name='producer'
|
||||||
|
|
|
@ -3,47 +3,53 @@ from typing import Optional
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Manager, QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from apps.rsform.models import LibraryItem, LibraryItemType
|
from apps.library.models import LibraryItem, LibraryItemType
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
|
|
||||||
from .Argument import Argument
|
from .Argument import Argument
|
||||||
from .Operation import Operation
|
from .Operation import Operation
|
||||||
from .SynthesisSubstitution import SynthesisSubstitution
|
from .Substitution import Substitution
|
||||||
|
|
||||||
|
|
||||||
class OperationSchema(LibraryItem):
|
class OperationSchema:
|
||||||
''' Operations schema API. '''
|
''' Operations schema API. '''
|
||||||
|
|
||||||
class Meta:
|
def __init__(self, model: LibraryItem):
|
||||||
''' Model metadata. '''
|
self.model = model
|
||||||
proxy = True
|
|
||||||
|
|
||||||
class InternalManager(Manager):
|
@staticmethod
|
||||||
''' Object manager. '''
|
def create(**kwargs) -> 'OperationSchema':
|
||||||
|
''' Create LibraryItem via OperationSchema. '''
|
||||||
|
model = LibraryItem.objects.create(item_type=LibraryItemType.OPERATION_SCHEMA, **kwargs)
|
||||||
|
return OperationSchema(model)
|
||||||
|
|
||||||
def get_queryset(self) -> QuerySet:
|
@staticmethod
|
||||||
return super().get_queryset().filter(item_type=LibraryItemType.OPERATION_SCHEMA)
|
def from_id(pk: int) -> 'OperationSchema':
|
||||||
|
''' Get LibraryItem by pk. '''
|
||||||
|
model = LibraryItem.objects.get(pk=pk)
|
||||||
|
return OperationSchema(model)
|
||||||
|
|
||||||
def create(self, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
kwargs.update({'item_type': LibraryItemType.OPERATION_SCHEMA})
|
''' Save wrapper. '''
|
||||||
return super().create(**kwargs)
|
self.model.save(*args, **kwargs)
|
||||||
|
|
||||||
# Legit overriding object manager
|
def refresh_from_db(self):
|
||||||
objects = InternalManager() # type: ignore[misc]
|
''' Model wrapper. '''
|
||||||
|
self.model.refresh_from_db()
|
||||||
|
|
||||||
def operations(self) -> QuerySet[Operation]:
|
def operations(self) -> QuerySet[Operation]:
|
||||||
''' Get QuerySet containing all operations of current OSS. '''
|
''' Get QuerySet containing all operations of current OSS. '''
|
||||||
return Operation.objects.filter(oss=self)
|
return Operation.objects.filter(oss=self.model)
|
||||||
|
|
||||||
def arguments(self) -> QuerySet[Argument]:
|
def arguments(self) -> QuerySet[Argument]:
|
||||||
''' Operation arguments. '''
|
''' Operation arguments. '''
|
||||||
return Argument.objects.filter(operation__oss=self)
|
return Argument.objects.filter(operation__oss=self.model)
|
||||||
|
|
||||||
def substitutions(self) -> QuerySet[SynthesisSubstitution]:
|
def substitutions(self) -> QuerySet[Substitution]:
|
||||||
''' Operation substitutions. '''
|
''' Operation substitutions. '''
|
||||||
return SynthesisSubstitution.objects.filter(operation__oss=self)
|
return Substitution.objects.filter(operation__oss=self.model)
|
||||||
|
|
||||||
def update_positions(self, data: list[dict]):
|
def update_positions(self, data: list[dict]):
|
||||||
''' Update positions. '''
|
''' Update positions. '''
|
||||||
|
@ -60,7 +66,7 @@ class OperationSchema(LibraryItem):
|
||||||
''' Insert new operation. '''
|
''' Insert new operation. '''
|
||||||
if kwargs['alias'] != '' and self.operations().filter(alias=kwargs['alias']).exists():
|
if kwargs['alias'] != '' and self.operations().filter(alias=kwargs['alias']).exists():
|
||||||
raise ValidationError(msg.aliasTaken(kwargs['alias']))
|
raise ValidationError(msg.aliasTaken(kwargs['alias']))
|
||||||
result = Operation.objects.create(oss=self, **kwargs)
|
result = Operation.objects.create(oss=self.model, **kwargs)
|
||||||
self.save()
|
self.save()
|
||||||
result.refresh_from_db()
|
result.refresh_from_db()
|
||||||
return result
|
return result
|
||||||
|
@ -109,7 +115,7 @@ class OperationSchema(LibraryItem):
|
||||||
return
|
return
|
||||||
|
|
||||||
Argument.objects.filter(operation=target).delete()
|
Argument.objects.filter(operation=target).delete()
|
||||||
SynthesisSubstitution.objects.filter(operation=target).delete()
|
Substitution.objects.filter(operation=target).delete()
|
||||||
|
|
||||||
# trigger on_change effects
|
# trigger on_change effects
|
||||||
|
|
||||||
|
@ -118,9 +124,9 @@ class OperationSchema(LibraryItem):
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def set_substitutions(self, target: Operation, substitutes: list[dict]):
|
def set_substitutions(self, target: Operation, substitutes: list[dict]):
|
||||||
''' Clear all arguments for operation. '''
|
''' Clear all arguments for operation. '''
|
||||||
SynthesisSubstitution.objects.filter(operation=target).delete()
|
Substitution.objects.filter(operation=target).delete()
|
||||||
for sub in substitutes:
|
for sub in substitutes:
|
||||||
SynthesisSubstitution.objects.create(
|
Substitution.objects.create(
|
||||||
operation=target,
|
operation=target,
|
||||||
original=sub['original'],
|
original=sub['original'],
|
||||||
substitution=sub['substitution'],
|
substitution=sub['substitution'],
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from django.db.models import CASCADE, BooleanField, ForeignKey, Model
|
from django.db.models import CASCADE, BooleanField, ForeignKey, Model
|
||||||
|
|
||||||
|
|
||||||
class SynthesisSubstitution(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 = ForeignKey(
|
||||||
verbose_name='Операция',
|
verbose_name='Операция',
|
|
@ -1,8 +1,6 @@
|
||||||
''' Django: Models. '''
|
''' Django: Models. '''
|
||||||
|
|
||||||
from apps.rsform.models import LibraryItem, LibraryItemType
|
|
||||||
|
|
||||||
from .Argument import Argument
|
from .Argument import Argument
|
||||||
from .Operation import Operation, OperationType
|
from .Operation import Operation, OperationType
|
||||||
from .OperationSchema import OperationSchema
|
from .OperationSchema import OperationSchema
|
||||||
from .SynthesisSubstitution import SynthesisSubstitution
|
from .Substitution import Substitution
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
''' REST API: Serializers. '''
|
''' REST API: Serializers. '''
|
||||||
|
|
||||||
from apps.rsform.serializers import LibraryItemSerializer
|
|
||||||
|
|
||||||
from .basics import OperationPositionSerializer, PositionsSerializer, SubstitutionExSerializer
|
from .basics import OperationPositionSerializer, PositionsSerializer, SubstitutionExSerializer
|
||||||
from .data_access import (
|
from .data_access import (
|
||||||
ArgumentSerializer,
|
ArgumentSerializer,
|
||||||
|
@ -10,4 +8,4 @@ from .data_access import (
|
||||||
OperationSchemaSerializer,
|
OperationSchemaSerializer,
|
||||||
OperationSerializer
|
OperationSerializer
|
||||||
)
|
)
|
||||||
from .schema_typing import NewOperationResponse
|
from .responses import NewOperationResponse
|
||||||
|
|
|
@ -5,8 +5,8 @@ from django.db.models import F
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
||||||
|
|
||||||
from apps.rsform.models import LibraryItem
|
from apps.library.models import LibraryItem
|
||||||
from apps.rsform.serializers import LibraryItemDetailsSerializer
|
from apps.library.serializers import LibraryItemDetailsSerializer
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
|
|
||||||
from ..models import Argument, Operation, OperationSchema, OperationType
|
from ..models import Argument, Operation, OperationSchema, OperationType
|
||||||
|
@ -85,19 +85,20 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
model = OperationSchema
|
model = LibraryItem
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def to_representation(self, instance: OperationSchema):
|
def to_representation(self, instance: LibraryItem):
|
||||||
result = LibraryItemDetailsSerializer(instance).data
|
result = LibraryItemDetailsSerializer(instance).data
|
||||||
|
oss = OperationSchema(instance)
|
||||||
result['items'] = []
|
result['items'] = []
|
||||||
for operation in instance.operations():
|
for operation in oss.operations():
|
||||||
result['items'].append(OperationSerializer(operation).data)
|
result['items'].append(OperationSerializer(operation).data)
|
||||||
result['arguments'] = []
|
result['arguments'] = []
|
||||||
for argument in instance.arguments():
|
for argument in oss.arguments():
|
||||||
result['arguments'].append(ArgumentSerializer(argument).data)
|
result['arguments'].append(ArgumentSerializer(argument).data)
|
||||||
result['substitutions'] = []
|
result['substitutions'] = []
|
||||||
for substitution in instance.substitutions().values(
|
for substitution in oss.substitutions().values(
|
||||||
'operation',
|
'operation',
|
||||||
'original',
|
'original',
|
||||||
'substitution',
|
'substitution',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
''' Tests for Django Models. '''
|
''' Tests for Django Models. '''
|
||||||
from .t_Argument import *
|
from .t_Argument import *
|
||||||
from .t_Operation import *
|
from .t_Operation import *
|
||||||
from .t_SynthesisSubstitution import *
|
from .t_Substitution import *
|
||||||
|
|
|
@ -8,11 +8,23 @@ class TestArgument(TestCase):
|
||||||
''' Testing Argument model. '''
|
''' Testing Argument model. '''
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.oss = OperationSchema.objects.create(alias='T1')
|
self.oss = OperationSchema.create(alias='T1')
|
||||||
|
|
||||||
self.operation1 = Operation.objects.create(oss=self.oss, alias='KS1', operation_type=OperationType.INPUT)
|
self.operation1 = Operation.objects.create(
|
||||||
self.operation2 = Operation.objects.create(oss=self.oss, alias='KS2', operation_type=OperationType.SYNTHESIS)
|
oss=self.oss.model,
|
||||||
self.operation3 = Operation.objects.create(oss=self.oss, alias='KS3', operation_type=OperationType.INPUT)
|
alias='KS1',
|
||||||
|
operation_type=OperationType.INPUT
|
||||||
|
)
|
||||||
|
self.operation2 = Operation.objects.create(
|
||||||
|
oss=self.oss.model,
|
||||||
|
alias='KS2',
|
||||||
|
operation_type=OperationType.SYNTHESIS
|
||||||
|
)
|
||||||
|
self.operation3 = Operation.objects.create(
|
||||||
|
oss=self.oss.model,
|
||||||
|
alias='KS3',
|
||||||
|
operation_type=OperationType.INPUT
|
||||||
|
)
|
||||||
self.argument = Argument.objects.create(
|
self.argument = Argument.objects.create(
|
||||||
operation=self.operation2,
|
operation=self.operation2,
|
||||||
argument=self.operation1
|
argument=self.operation1
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
''' Testing models: Operation. '''
|
''' Testing models: Operation. '''
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from apps.oss.models import LibraryItem, LibraryItemType, Operation, OperationSchema, OperationType
|
from apps.library.models import LibraryItem, LibraryItemType
|
||||||
|
from apps.oss.models import Operation, OperationSchema, OperationType
|
||||||
from apps.rsform.models import RSForm
|
from apps.rsform.models import RSForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,9 +10,9 @@ class TestOperation(TestCase):
|
||||||
''' Testing Operation model. '''
|
''' Testing Operation model. '''
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.oss = OperationSchema.objects.create(alias='T1')
|
self.oss = OperationSchema.create(alias='T1')
|
||||||
self.operation = Operation.objects.create(
|
self.operation = Operation.objects.create(
|
||||||
oss=self.oss,
|
oss=self.oss.model,
|
||||||
alias='KS1'
|
alias='KS1'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ class TestOperation(TestCase):
|
||||||
|
|
||||||
|
|
||||||
def test_create_default(self):
|
def test_create_default(self):
|
||||||
self.assertEqual(self.operation.oss, self.oss)
|
self.assertEqual(self.operation.oss, self.oss.model)
|
||||||
self.assertEqual(self.operation.operation_type, OperationType.INPUT)
|
self.assertEqual(self.operation.operation_type, OperationType.INPUT)
|
||||||
self.assertEqual(self.operation.result, None)
|
self.assertEqual(self.operation.result, None)
|
||||||
self.assertEqual(self.operation.alias, 'KS1')
|
self.assertEqual(self.operation.alias, 'KS1')
|
||||||
|
@ -34,29 +35,29 @@ class TestOperation(TestCase):
|
||||||
|
|
||||||
|
|
||||||
def test_sync_from_result(self):
|
def test_sync_from_result(self):
|
||||||
schema = RSForm.objects.create(alias=self.operation.alias)
|
schema = RSForm.create(alias=self.operation.alias)
|
||||||
self.operation.result = schema
|
self.operation.result = schema.model
|
||||||
self.operation.save()
|
self.operation.save()
|
||||||
|
|
||||||
schema.alias = 'KS2'
|
schema.model.alias = 'KS2'
|
||||||
schema.comment = 'Comment'
|
schema.model.comment = 'Comment'
|
||||||
schema.title = 'Title'
|
schema.model.title = 'Title'
|
||||||
schema.save()
|
schema.save()
|
||||||
self.operation.refresh_from_db()
|
self.operation.refresh_from_db()
|
||||||
|
|
||||||
self.assertEqual(self.operation.result, schema)
|
self.assertEqual(self.operation.result, schema.model)
|
||||||
self.assertEqual(self.operation.alias, schema.alias)
|
self.assertEqual(self.operation.alias, schema.model.alias)
|
||||||
self.assertEqual(self.operation.title, schema.title)
|
self.assertEqual(self.operation.title, schema.model.title)
|
||||||
self.assertEqual(self.operation.comment, schema.comment)
|
self.assertEqual(self.operation.comment, schema.model.comment)
|
||||||
|
|
||||||
self.operation.sync_text = False
|
self.operation.sync_text = False
|
||||||
self.operation.save()
|
self.operation.save()
|
||||||
|
|
||||||
schema.alias = 'KS3'
|
schema.model.alias = 'KS3'
|
||||||
schema.save()
|
schema.save()
|
||||||
self.operation.refresh_from_db()
|
self.operation.refresh_from_db()
|
||||||
self.assertEqual(self.operation.result, schema)
|
self.assertEqual(self.operation.result, schema.model)
|
||||||
self.assertNotEqual(self.operation.alias, schema.alias)
|
self.assertNotEqual(self.operation.alias, schema.model.alias)
|
||||||
|
|
||||||
def test_sync_from_library_item(self):
|
def test_sync_from_library_item(self):
|
||||||
schema = LibraryItem.objects.create(alias=self.operation.alias, item_type=LibraryItemType.RSFORM)
|
schema = LibraryItem.objects.create(alias=self.operation.alias, item_type=LibraryItemType.RSFORM)
|
||||||
|
|
|
@ -3,13 +3,7 @@ from unittest import result
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from apps.oss.models import (
|
from apps.oss.models import Argument, Operation, OperationSchema, OperationType, Substitution
|
||||||
Argument,
|
|
||||||
Operation,
|
|
||||||
OperationSchema,
|
|
||||||
OperationType,
|
|
||||||
SynthesisSubstitution
|
|
||||||
)
|
|
||||||
from apps.rsform.models import RSForm
|
from apps.rsform.models import RSForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,24 +11,30 @@ class TestSynthesisSubstitution(TestCase):
|
||||||
''' Testing Synthesis Substitution model. '''
|
''' Testing Synthesis Substitution model. '''
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.oss = OperationSchema.objects.create(alias='T1')
|
self.oss = OperationSchema.create(alias='T1')
|
||||||
|
|
||||||
self.ks1 = RSForm.objects.create(alias='KS1', title='Test1')
|
self.ks1 = RSForm.create(alias='KS1', title='Test1')
|
||||||
self.ks1x1 = self.ks1.insert_new('X1', term_resolved='X1_1')
|
self.ks1x1 = self.ks1.insert_new('X1', term_resolved='X1_1')
|
||||||
self.ks2 = RSForm.objects.create(alias='KS2', title='Test2')
|
self.ks2 = RSForm.create(alias='KS2', title='Test2')
|
||||||
self.ks2x1 = self.ks2.insert_new('X2', term_resolved='X1_2')
|
self.ks2x1 = self.ks2.insert_new('X2', term_resolved='X1_2')
|
||||||
|
|
||||||
self.operation1 = Operation.objects.create(
|
self.operation1 = Operation.objects.create(
|
||||||
oss=self.oss,
|
oss=self.oss.model,
|
||||||
alias='KS1',
|
alias='KS1',
|
||||||
operation_type=OperationType.INPUT,
|
operation_type=OperationType.INPUT,
|
||||||
result=self.ks1)
|
result=self.ks1.model
|
||||||
|
)
|
||||||
self.operation2 = Operation.objects.create(
|
self.operation2 = Operation.objects.create(
|
||||||
oss=self.oss,
|
oss=self.oss.model,
|
||||||
alias='KS2',
|
alias='KS2',
|
||||||
operation_type=OperationType.INPUT,
|
operation_type=OperationType.INPUT,
|
||||||
result=self.ks1)
|
result=self.ks1.model
|
||||||
self.operation3 = Operation.objects.create(oss=self.oss, alias='KS3', operation_type=OperationType.SYNTHESIS)
|
)
|
||||||
|
self.operation3 = Operation.objects.create(
|
||||||
|
oss=self.oss.model,
|
||||||
|
alias='KS3',
|
||||||
|
operation_type=OperationType.SYNTHESIS
|
||||||
|
)
|
||||||
Argument.objects.create(
|
Argument.objects.create(
|
||||||
operation=self.operation3,
|
operation=self.operation3,
|
||||||
argument=self.operation1
|
argument=self.operation1
|
||||||
|
@ -44,7 +44,7 @@ class TestSynthesisSubstitution(TestCase):
|
||||||
argument=self.operation2
|
argument=self.operation2
|
||||||
)
|
)
|
||||||
|
|
||||||
self.substitution = SynthesisSubstitution.objects.create(
|
self.substitution = Substitution.objects.create(
|
||||||
operation=self.operation3,
|
operation=self.operation3,
|
||||||
original=self.ks1x1,
|
original=self.ks1x1,
|
||||||
substitution=self.ks2x1,
|
substitution=self.ks2x1,
|
||||||
|
@ -58,18 +58,18 @@ class TestSynthesisSubstitution(TestCase):
|
||||||
|
|
||||||
|
|
||||||
def test_cascade_delete_operation(self):
|
def test_cascade_delete_operation(self):
|
||||||
self.assertEqual(SynthesisSubstitution.objects.count(), 1)
|
self.assertEqual(Substitution.objects.count(), 1)
|
||||||
self.operation3.delete()
|
self.operation3.delete()
|
||||||
self.assertEqual(SynthesisSubstitution.objects.count(), 0)
|
self.assertEqual(Substitution.objects.count(), 0)
|
||||||
|
|
||||||
|
|
||||||
def test_cascade_delete_original(self):
|
def test_cascade_delete_original(self):
|
||||||
self.assertEqual(SynthesisSubstitution.objects.count(), 1)
|
self.assertEqual(Substitution.objects.count(), 1)
|
||||||
self.ks1x1.delete()
|
self.ks1x1.delete()
|
||||||
self.assertEqual(SynthesisSubstitution.objects.count(), 0)
|
self.assertEqual(Substitution.objects.count(), 0)
|
||||||
|
|
||||||
|
|
||||||
def test_cascade_delete_substitution(self):
|
def test_cascade_delete_substitution(self):
|
||||||
self.assertEqual(SynthesisSubstitution.objects.count(), 1)
|
self.assertEqual(Substitution.objects.count(), 1)
|
||||||
self.ks2x1.delete()
|
self.ks2x1.delete()
|
||||||
self.assertEqual(SynthesisSubstitution.objects.count(), 0)
|
self.assertEqual(Substitution.objects.count(), 0)
|
|
@ -2,8 +2,9 @@
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
from apps.library.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead
|
||||||
from apps.oss.models import Operation, OperationSchema, OperationType
|
from apps.oss.models import Operation, OperationSchema, OperationType
|
||||||
from apps.rsform.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead, RSForm
|
from apps.rsform.models import RSForm
|
||||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,29 +13,29 @@ class TestOssViewset(EndpointTester):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.owned = OperationSchema.objects.create(title='Test', alias='T1', owner=self.user)
|
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
|
||||||
self.owned_id = self.owned.pk
|
self.owned_id = self.owned.model.pk
|
||||||
self.unowned = OperationSchema.objects.create(title='Test2', alias='T2')
|
self.unowned = OperationSchema.create(title='Test2', alias='T2')
|
||||||
self.unowned_id = self.unowned.pk
|
self.unowned_id = self.unowned.model.pk
|
||||||
self.private = OperationSchema.objects.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
self.private = OperationSchema.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
||||||
self.private_id = self.private.pk
|
self.private_id = self.private.model.pk
|
||||||
self.invalid_id = self.private.pk + 1337
|
self.invalid_id = self.private.model.pk + 1337
|
||||||
|
|
||||||
|
|
||||||
def populateData(self):
|
def populateData(self):
|
||||||
self.ks1 = RSForm.objects.create(alias='KS1', title='Test1')
|
self.ks1 = RSForm.create(alias='KS1', title='Test1', owner=self.user)
|
||||||
self.ks1x1 = self.ks1.insert_new('X1', term_resolved='X1_1')
|
self.ks1x1 = self.ks1.insert_new('X1', term_resolved='X1_1')
|
||||||
self.ks2 = RSForm.objects.create(alias='KS2', title='Test2')
|
self.ks2 = RSForm.create(alias='KS2', title='Test2', owner=self.user)
|
||||||
self.ks2x1 = self.ks2.insert_new('X2', term_resolved='X1_2')
|
self.ks2x1 = self.ks2.insert_new('X2', term_resolved='X1_2')
|
||||||
self.operation1 = self.owned.create_operation(
|
self.operation1 = self.owned.create_operation(
|
||||||
alias='1',
|
alias='1',
|
||||||
operation_type=OperationType.INPUT,
|
operation_type=OperationType.INPUT,
|
||||||
result=self.ks1
|
result=self.ks1.model
|
||||||
)
|
)
|
||||||
self.operation2 = self.owned.create_operation(
|
self.operation2 = self.owned.create_operation(
|
||||||
alias='2',
|
alias='2',
|
||||||
operation_type=OperationType.INPUT,
|
operation_type=OperationType.INPUT,
|
||||||
result=self.ks2
|
result=self.ks2.model
|
||||||
)
|
)
|
||||||
self.operation3 = self.owned.create_operation(
|
self.operation3 = self.owned.create_operation(
|
||||||
alias='3',
|
alias='3',
|
||||||
|
@ -53,12 +54,12 @@ class TestOssViewset(EndpointTester):
|
||||||
self.populateData()
|
self.populateData()
|
||||||
|
|
||||||
response = self.executeOK(item=self.owned_id)
|
response = self.executeOK(item=self.owned_id)
|
||||||
self.assertEqual(response.data['owner'], self.owned.owner.pk)
|
self.assertEqual(response.data['owner'], self.owned.model.owner.pk)
|
||||||
self.assertEqual(response.data['title'], self.owned.title)
|
self.assertEqual(response.data['title'], self.owned.model.title)
|
||||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
self.assertEqual(response.data['alias'], self.owned.model.alias)
|
||||||
self.assertEqual(response.data['location'], self.owned.location)
|
self.assertEqual(response.data['location'], self.owned.model.location)
|
||||||
self.assertEqual(response.data['access_policy'], self.owned.access_policy)
|
self.assertEqual(response.data['access_policy'], self.owned.model.access_policy)
|
||||||
self.assertEqual(response.data['visible'], self.owned.visible)
|
self.assertEqual(response.data['visible'], self.owned.model.visible)
|
||||||
|
|
||||||
self.assertEqual(response.data['item_type'], LibraryItemType.OPERATION_SCHEMA)
|
self.assertEqual(response.data['item_type'], LibraryItemType.OPERATION_SCHEMA)
|
||||||
|
|
||||||
|
@ -121,12 +122,12 @@ class TestOssViewset(EndpointTester):
|
||||||
self.assertEqual(self.operation2.position_y, data['positions'][1]['position_y'])
|
self.assertEqual(self.operation2.position_y, data['positions'][1]['position_y'])
|
||||||
|
|
||||||
self.executeForbidden(data=data, item=self.unowned_id)
|
self.executeForbidden(data=data, item=self.unowned_id)
|
||||||
self.executeForbidden(item=self.private_id)
|
self.executeForbidden(data=data, item=self.private_id)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||||
def test_create_operation(self):
|
def test_create_operation(self):
|
||||||
self.executeNotFound(item=self.invalid_id)
|
|
||||||
|
|
||||||
self.populateData()
|
self.populateData()
|
||||||
self.executeBadData(item=self.owned_id)
|
self.executeBadData(item=self.owned_id)
|
||||||
|
@ -150,7 +151,9 @@ class TestOssViewset(EndpointTester):
|
||||||
self.executeBadData(data=data)
|
self.executeBadData(data=data)
|
||||||
|
|
||||||
data['item_data']['operation_type'] = OperationType.INPUT
|
data['item_data']['operation_type'] = OperationType.INPUT
|
||||||
response = self.executeCreated(data=data)
|
self.executeNotFound(data=data, item=self.invalid_id)
|
||||||
|
|
||||||
|
response = self.executeCreated(data=data, item=self.owned_id)
|
||||||
self.assertEqual(len(response.data['oss']['items']), 4)
|
self.assertEqual(len(response.data['oss']['items']), 4)
|
||||||
new_operation = response.data['new_operation']
|
new_operation = response.data['new_operation']
|
||||||
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
|
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
|
||||||
|
@ -195,14 +198,14 @@ class TestOssViewset(EndpointTester):
|
||||||
'item_data': {
|
'item_data': {
|
||||||
'alias': 'Test4',
|
'alias': 'Test4',
|
||||||
'operation_type': OperationType.INPUT,
|
'operation_type': OperationType.INPUT,
|
||||||
'result': self.ks1.pk
|
'result': self.ks1.model.pk
|
||||||
},
|
},
|
||||||
'positions': [],
|
'positions': [],
|
||||||
}
|
}
|
||||||
response = self.executeCreated(data=data, item=self.owned_id)
|
response = self.executeCreated(data=data, item=self.owned_id)
|
||||||
self.owned.refresh_from_db()
|
self.owned.refresh_from_db()
|
||||||
new_operation = response.data['new_operation']
|
new_operation = response.data['new_operation']
|
||||||
self.assertEqual(new_operation['result'], self.ks1.pk)
|
self.assertEqual(new_operation['result'], self.ks1.model.pk)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||||
|
|
|
@ -4,9 +4,9 @@ from rest_framework import routers
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
library_router = routers.SimpleRouter(trailing_slash=False)
|
oss_router = routers.SimpleRouter(trailing_slash=False)
|
||||||
library_router.register('oss', views.OssViewSet, 'OSS')
|
oss_router.register('oss', views.OssViewSet, 'OSS')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(library_router.urls)),
|
path('', include(oss_router.urls)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,6 +10,8 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from apps.library.models import LibraryItem, LibraryItemType
|
||||||
|
from apps.library.serializers import LibraryItemSerializer
|
||||||
from shared import permissions
|
from shared import permissions
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
|
@ -20,11 +22,11 @@ from .. import serializers as s
|
||||||
@extend_schema_view()
|
@extend_schema_view()
|
||||||
class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
||||||
''' Endpoint: OperationSchema. '''
|
''' Endpoint: OperationSchema. '''
|
||||||
queryset = m.OperationSchema.objects.all()
|
queryset = LibraryItem.objects.filter(item_type=LibraryItemType.OPERATION_SCHEMA)
|
||||||
serializer_class = s.LibraryItemSerializer
|
serializer_class = LibraryItemSerializer
|
||||||
|
|
||||||
def _get_schema(self) -> m.OperationSchema:
|
def _get_item(self) -> LibraryItem:
|
||||||
return cast(m.OperationSchema, self.get_object())
|
return cast(LibraryItem, self.get_object())
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
''' Determine permission class. '''
|
''' Determine permission class. '''
|
||||||
|
@ -52,7 +54,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
@action(detail=True, methods=['get'], url_path='details')
|
@action(detail=True, methods=['get'], url_path='details')
|
||||||
def details(self, request: Request, pk):
|
def details(self, request: Request, pk):
|
||||||
''' Endpoint: Detailed OSS data. '''
|
''' Endpoint: Detailed OSS data. '''
|
||||||
serializer = s.OperationSchemaSerializer(self._get_schema())
|
serializer = s.OperationSchemaSerializer(self._get_item())
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=serializer.data
|
data=serializer.data
|
||||||
|
@ -71,10 +73,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
@action(detail=True, methods=['patch'], url_path='update-positions')
|
@action(detail=True, methods=['patch'], url_path='update-positions')
|
||||||
def update_positions(self, request: Request, pk):
|
def update_positions(self, request: Request, pk):
|
||||||
''' Endpoint: Update operations positions. '''
|
''' Endpoint: Update operations positions. '''
|
||||||
schema = self._get_schema()
|
|
||||||
serializer = s.PositionsSerializer(data=request.data)
|
serializer = s.PositionsSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema.update_positions(serializer.validated_data['positions'])
|
m.OperationSchema(self.get_object()).update_positions(serializer.validated_data['positions'])
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -91,23 +92,23 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
@action(detail=True, methods=['post'], url_path='create-operation')
|
@action(detail=True, methods=['post'], url_path='create-operation')
|
||||||
def create_operation(self, request: Request, pk):
|
def create_operation(self, request: Request, pk):
|
||||||
''' Create new operation. '''
|
''' Create new operation. '''
|
||||||
schema = self._get_schema()
|
|
||||||
serializer = s.OperationCreateSerializer(data=request.data)
|
serializer = s.OperationCreateSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
oss = m.OperationSchema(self.get_object())
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
schema.update_positions(serializer.validated_data['positions'])
|
oss.update_positions(serializer.validated_data['positions'])
|
||||||
new_operation = schema.create_operation(**serializer.validated_data['item_data'])
|
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
|
||||||
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
|
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
|
||||||
for argument in serializer.validated_data['arguments']:
|
for argument in serializer.validated_data['arguments']:
|
||||||
schema.add_argument(operation=new_operation, argument=argument)
|
oss.add_argument(operation=new_operation, argument=argument)
|
||||||
schema.refresh_from_db()
|
|
||||||
|
|
||||||
|
oss.refresh_from_db()
|
||||||
response = Response(
|
response = Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
data={
|
data={
|
||||||
'new_operation': s.OperationSerializer(new_operation).data,
|
'new_operation': s.OperationSerializer(new_operation).data,
|
||||||
'oss': s.OperationSchemaSerializer(schema).data
|
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
@ -126,19 +127,19 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
||||||
def delete_operation(self, request: Request, pk):
|
def delete_operation(self, request: Request, pk):
|
||||||
''' Endpoint: Delete operation. '''
|
''' Endpoint: Delete operation. '''
|
||||||
schema = self._get_schema()
|
|
||||||
serializer = s.OperationDeleteSerializer(
|
serializer = s.OperationDeleteSerializer(
|
||||||
data=request.data,
|
data=request.data,
|
||||||
context={'oss': schema}
|
context={'oss': self.get_object()}
|
||||||
)
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
oss = m.OperationSchema(self.get_object())
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
schema.update_positions(serializer.validated_data['positions'])
|
oss.update_positions(serializer.validated_data['positions'])
|
||||||
schema.delete_operation(serializer.validated_data['target'])
|
oss.delete_operation(serializer.validated_data['target'])
|
||||||
schema.refresh_from_db()
|
|
||||||
|
|
||||||
|
oss.refresh_from_db()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.OperationSchemaSerializer(schema).data
|
data=s.OperationSchemaSerializer(oss.model).data
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,60 +10,4 @@ class ConstituentaAdmin(admin.ModelAdmin):
|
||||||
list_display = ['schema', 'alias', 'term_resolved', 'definition_resolved']
|
list_display = ['schema', 'alias', 'term_resolved', 'definition_resolved']
|
||||||
search_fields = ['term_resolved', 'definition_resolved']
|
search_fields = ['term_resolved', 'definition_resolved']
|
||||||
|
|
||||||
|
|
||||||
class LibraryItemAdmin(admin.ModelAdmin):
|
|
||||||
''' Admin model: LibraryItem. '''
|
|
||||||
date_hierarchy = 'time_update'
|
|
||||||
list_display = [
|
|
||||||
'alias', 'title', 'owner',
|
|
||||||
'visible', 'read_only', 'access_policy', 'location',
|
|
||||||
'time_update'
|
|
||||||
]
|
|
||||||
list_filter = ['visible', 'read_only', 'access_policy', 'location', 'time_update']
|
|
||||||
search_fields = ['alias', 'title', 'location']
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryTemplateAdmin(admin.ModelAdmin):
|
|
||||||
''' Admin model: LibraryTemplate. '''
|
|
||||||
list_display = ['id', 'alias']
|
|
||||||
list_select_related = ['lib_source']
|
|
||||||
|
|
||||||
def alias(self, template: models.LibraryTemplate):
|
|
||||||
if template.lib_source:
|
|
||||||
return template.lib_source.alias
|
|
||||||
else:
|
|
||||||
return 'N/A'
|
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionAdmin(admin.ModelAdmin):
|
|
||||||
''' Admin model: Subscriptions. '''
|
|
||||||
list_display = ['id', 'item', 'user']
|
|
||||||
search_fields = [
|
|
||||||
'item__title', 'item__alias',
|
|
||||||
'user__username', 'user__first_name', 'user__last_name'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EditorAdmin(admin.ModelAdmin):
|
|
||||||
''' Admin model: Editors. '''
|
|
||||||
list_display = ['id', 'item', 'editor']
|
|
||||||
search_fields = [
|
|
||||||
'item__title', 'item__alias',
|
|
||||||
'editor__username', 'editor__first_name', 'editor__last_name'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class VersionAdmin(admin.ModelAdmin):
|
|
||||||
''' Admin model: Versions. '''
|
|
||||||
list_display = ['id', 'item', 'version', 'description', 'time_create']
|
|
||||||
search_fields = [
|
|
||||||
'item__title', 'item__alias'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(models.Constituenta, ConstituentaAdmin)
|
admin.site.register(models.Constituenta, ConstituentaAdmin)
|
||||||
admin.site.register(models.LibraryItem, LibraryItemAdmin)
|
|
||||||
admin.site.register(models.LibraryTemplate, LibraryTemplateAdmin)
|
|
||||||
admin.site.register(models.Subscription, SubscriptionAdmin)
|
|
||||||
admin.site.register(models.Version, VersionAdmin)
|
|
||||||
admin.site.register(models.Editor, EditorAdmin)
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
# Generated by Django 4.2.4 on 2023-08-26 10:09
|
# Generated by Django 5.0.7 on 2024-07-25 16:06
|
||||||
|
|
||||||
import apps.rsform.models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -12,29 +10,10 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
('library', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
|
||||||
name='LibraryItem',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('item_type', models.CharField(choices=[('rsform', 'Rsform'), ('oss', 'Operations Schema')], max_length=50, verbose_name='Тип')),
|
|
||||||
('title', models.TextField(verbose_name='Название')),
|
|
||||||
('alias', models.CharField(blank=True, max_length=255, verbose_name='Шифр')),
|
|
||||||
('comment', models.TextField(blank=True, verbose_name='Комментарий')),
|
|
||||||
('is_common', models.BooleanField(default=False, verbose_name='Общая')),
|
|
||||||
('is_canonical', models.BooleanField(default=False, verbose_name='Каноничная')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
|
|
||||||
('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Владелец')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Схема',
|
|
||||||
'verbose_name_plural': 'Схемы',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Constituenta',
|
name='Constituenta',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -45,28 +24,15 @@ class Migration(migrations.Migration):
|
||||||
('convention', models.TextField(blank=True, default='', verbose_name='Комментарий/Конвенция')),
|
('convention', models.TextField(blank=True, default='', verbose_name='Комментарий/Конвенция')),
|
||||||
('term_raw', models.TextField(blank=True, default='', verbose_name='Термин (с отсылками)')),
|
('term_raw', models.TextField(blank=True, default='', verbose_name='Термин (с отсылками)')),
|
||||||
('term_resolved', models.TextField(blank=True, default='', verbose_name='Термин')),
|
('term_resolved', models.TextField(blank=True, default='', verbose_name='Термин')),
|
||||||
('term_forms', models.JSONField(default=apps.rsform.models._empty_forms, verbose_name='Словоформы')),
|
('term_forms', models.JSONField(default=list, verbose_name='Словоформы')),
|
||||||
('definition_formal', models.TextField(blank=True, default='', verbose_name='Родоструктурное определение')),
|
('definition_formal', models.TextField(blank=True, default='', verbose_name='Родоструктурное определение')),
|
||||||
('definition_raw', models.TextField(blank=True, default='', verbose_name='Текстовое определние (с отсылками)')),
|
('definition_raw', models.TextField(blank=True, default='', verbose_name='Текстовое определение (с отсылками)')),
|
||||||
('definition_resolved', models.TextField(blank=True, default='', verbose_name='Текстовое определние')),
|
('definition_resolved', models.TextField(blank=True, default='', verbose_name='Текстовое определение')),
|
||||||
('schema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.libraryitem', verbose_name='Концептуальная схема')),
|
('schema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library.libraryitem', verbose_name='Концептуальная схема')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Конституента',
|
'verbose_name': 'Конституента',
|
||||||
'verbose_name_plural': 'Конституенты',
|
'verbose_name_plural': 'Конституенты',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='Subscription',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.libraryitem', verbose_name='Элемент')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Подписки',
|
|
||||||
'verbose_name_plural': 'Подписка',
|
|
||||||
'unique_together': {('user', 'item')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Generated by Django 4.2.6 on 2023-10-18 16:12
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rsform', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='LibraryTemplate',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('lib_source', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rsform.libraryitem', verbose_name='Источник')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Шаблон',
|
|
||||||
'verbose_name_plural': 'Шаблоны',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 4.2.7 on 2023-12-27 08:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rsform', '0002_librarytemplate'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='constituenta',
|
|
||||||
name='definition_raw',
|
|
||||||
field=models.TextField(blank=True, default='', verbose_name='Текстовое определение (с отсылками)'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='constituenta',
|
|
||||||
name='definition_resolved',
|
|
||||||
field=models.TextField(blank=True, default='', verbose_name='Текстовое определение'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Generated by Django 4.2.10 on 2024-03-03 10:57
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rsform', '0003_alter_constituenta_definition_raw_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Version',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('version', models.CharField(max_length=20, verbose_name='Версия')),
|
|
||||||
('description', models.TextField(blank=True, verbose_name='Описание')),
|
|
||||||
('data', models.JSONField(verbose_name='Содержание')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.libraryitem', verbose_name='Схема')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Версии',
|
|
||||||
'verbose_name_plural': 'Версия',
|
|
||||||
'unique_together': {('item', 'version')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,21 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-05-20 14:39
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rsform', '0004_version'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='subscription',
|
|
||||||
options={'verbose_name': 'Подписка', 'verbose_name_plural': 'Подписки'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='version',
|
|
||||||
options={'verbose_name': 'Версия', 'verbose_name_plural': 'Версии'},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-05-20 14:40
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rsform', '0005_alter_subscription_options_alter_version_options'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Editor',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата добавления')),
|
|
||||||
('editor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Редактор')),
|
|
||||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.libraryitem', verbose_name='Схема')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Редактор',
|
|
||||||
'verbose_name_plural': 'Редакторы',
|
|
||||||
'unique_together': {('item', 'editor')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,65 +0,0 @@
|
||||||
# Hand written migration 20240531
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
from .. import models as m
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rsform', '0006_editor'),
|
|
||||||
]
|
|
||||||
|
|
||||||
def calculate_location(apps, schema_editor):
|
|
||||||
LibraryItem = apps.get_model('rsform', 'LibraryItem')
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
for item in LibraryItem.objects.using(db_alias).all():
|
|
||||||
if item.is_canonical:
|
|
||||||
location = m.LocationHead.LIBRARY
|
|
||||||
elif item.is_common:
|
|
||||||
location = m.LocationHead.COMMON
|
|
||||||
else:
|
|
||||||
location = m.LocationHead.USER
|
|
||||||
item.location = location
|
|
||||||
item.save(update_fields=['location'])
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='libraryitem',
|
|
||||||
name='access_policy',
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[
|
|
||||||
('public', 'Public'),
|
|
||||||
('protected', 'Protected'),
|
|
||||||
('private', 'Private')
|
|
||||||
],
|
|
||||||
default='public',
|
|
||||||
max_length=500,
|
|
||||||
verbose_name='Политика доступа'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='libraryitem',
|
|
||||||
name='location',
|
|
||||||
field=models.TextField(default='/U', max_length=500, verbose_name='Расположение'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='libraryitem',
|
|
||||||
name='read_only',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Запретить редактирование'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='libraryitem',
|
|
||||||
name='visible',
|
|
||||||
field=models.BooleanField(default=True, verbose_name='Отображаемая'),
|
|
||||||
),
|
|
||||||
migrations.RunPython(calculate_location, migrations.RunPython.noop), # type: ignore
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='libraryitem',
|
|
||||||
name='is_canonical',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='libraryitem',
|
|
||||||
name='is_common',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.0.7 on 2024-07-17 09:51
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rsform', '0007_location_and_flags'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='libraryitem',
|
|
||||||
name='item_type',
|
|
||||||
field=models.CharField(choices=[('rsform', 'Rsform'), ('oss', 'Operation Schema')], max_length=50, verbose_name='Тип'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,35 +0,0 @@
|
||||||
# Generated by Django 5.0.7 on 2024-07-22 14:21
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rsform', '0008_alter_libraryitem_item_type'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='RSForm',
|
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'proxy': True,
|
|
||||||
'indexes': [],
|
|
||||||
'constraints': [],
|
|
||||||
},
|
|
||||||
bases=('rsform.libraryitem',),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='constituenta',
|
|
||||||
name='schema',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.rsform', verbose_name='Концептуальная схема'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='librarytemplate',
|
|
||||||
name='lib_source',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rsform.rsform', verbose_name='Источник'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -20,10 +20,6 @@ _REF_ENTITY_PATTERN = re.compile(r'@{([^0-9\-].*?)\|.*?}')
|
||||||
_GLOBAL_ID_PATTERN = re.compile(r'([XCSADFPT][0-9]+)') # cspell:disable-line
|
_GLOBAL_ID_PATTERN = re.compile(r'([XCSADFPT][0-9]+)') # cspell:disable-line
|
||||||
|
|
||||||
|
|
||||||
def _empty_forms():
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class CstType(TextChoices):
|
class CstType(TextChoices):
|
||||||
''' Type of constituenta. '''
|
''' Type of constituenta. '''
|
||||||
BASE = 'basic'
|
BASE = 'basic'
|
||||||
|
@ -40,7 +36,7 @@ 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 = ForeignKey(
|
||||||
verbose_name='Концептуальная схема',
|
verbose_name='Концептуальная схема',
|
||||||
to='rsform.RSForm',
|
to='library.LibraryItem',
|
||||||
on_delete=CASCADE
|
on_delete=CASCADE
|
||||||
)
|
)
|
||||||
order: PositiveIntegerField = PositiveIntegerField(
|
order: PositiveIntegerField = PositiveIntegerField(
|
||||||
|
@ -76,7 +72,7 @@ class Constituenta(Model):
|
||||||
)
|
)
|
||||||
term_forms: JSONField = JSONField(
|
term_forms: JSONField = JSONField(
|
||||||
verbose_name='Словоформы',
|
verbose_name='Словоформы',
|
||||||
default=_empty_forms
|
default=list
|
||||||
)
|
)
|
||||||
definition_formal: TextField = TextField(
|
definition_formal: TextField = TextField(
|
||||||
verbose_name='Родоструктурное определение',
|
verbose_name='Родоструктурное определение',
|
||||||
|
|
|
@ -5,8 +5,9 @@ from typing import Optional, cast
|
||||||
from cctext import Entity, Resolver, TermForm, extract_entities, split_grams
|
from cctext import Entity, Resolver, TermForm, extract_entities, split_grams
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Manager, QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
from apps.library.models import LibraryItem, LibraryItemType, Version
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
|
|
||||||
from ..graph import Graph
|
from ..graph import Graph
|
||||||
|
@ -22,35 +23,39 @@ from .api_RSLanguage import (
|
||||||
split_template
|
split_template
|
||||||
)
|
)
|
||||||
from .Constituenta import Constituenta, CstType
|
from .Constituenta import Constituenta, CstType
|
||||||
from .LibraryItem import LibraryItem, LibraryItemType
|
|
||||||
from .Version import Version
|
|
||||||
|
|
||||||
_INSERT_LAST: int = -1
|
_INSERT_LAST: int = -1
|
||||||
|
|
||||||
|
|
||||||
class RSForm(LibraryItem):
|
class RSForm:
|
||||||
''' RSForm is math form of conceptual schema. '''
|
''' RSForm is math form of conceptual schema. '''
|
||||||
|
|
||||||
class Meta:
|
def __init__(self, model: LibraryItem):
|
||||||
''' Model metadata. '''
|
self.model = model
|
||||||
proxy = True
|
|
||||||
|
|
||||||
class InternalManager(Manager):
|
@staticmethod
|
||||||
''' Object manager. '''
|
def create(**kwargs) -> 'RSForm':
|
||||||
|
''' Create LibraryItem via RSForm. '''
|
||||||
|
model = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, **kwargs)
|
||||||
|
return RSForm(model)
|
||||||
|
|
||||||
def get_queryset(self) -> QuerySet:
|
@staticmethod
|
||||||
return super().get_queryset().filter(item_type=LibraryItemType.RSFORM)
|
def from_id(pk: int) -> 'RSForm':
|
||||||
|
''' Get LibraryItem by pk. '''
|
||||||
|
model = LibraryItem.objects.get(pk=pk)
|
||||||
|
return RSForm(model)
|
||||||
|
|
||||||
def create(self, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
kwargs.update({'item_type': LibraryItemType.RSFORM})
|
''' Model wrapper. '''
|
||||||
return super().create(**kwargs)
|
self.model.save(*args, **kwargs)
|
||||||
|
|
||||||
# Legit overriding object manager
|
def refresh_from_db(self):
|
||||||
objects = InternalManager() # type: ignore[misc]
|
''' Model wrapper. '''
|
||||||
|
self.model.refresh_from_db()
|
||||||
|
|
||||||
def constituents(self) -> QuerySet[Constituenta]:
|
def constituents(self) -> QuerySet[Constituenta]:
|
||||||
''' Get QuerySet containing all constituents of current RSForm. '''
|
''' Get QuerySet containing all constituents of current RSForm. '''
|
||||||
return Constituenta.objects.filter(schema=self.pk)
|
return Constituenta.objects.filter(schema=self.model)
|
||||||
|
|
||||||
def resolver(self) -> Resolver:
|
def resolver(self) -> Resolver:
|
||||||
''' Create resolver for text references based on schema terms. '''
|
''' Create resolver for text references based on schema terms. '''
|
||||||
|
@ -106,7 +111,7 @@ class RSForm(LibraryItem):
|
||||||
''' Get maximum alias index for specific CstType. '''
|
''' Get maximum alias index for specific CstType. '''
|
||||||
result: int = 0
|
result: int = 0
|
||||||
items = Constituenta.objects \
|
items = Constituenta.objects \
|
||||||
.filter(schema=self, cst_type=cst_type) \
|
.filter(schema=self.model, cst_type=cst_type) \
|
||||||
.order_by('-alias') \
|
.order_by('-alias') \
|
||||||
.values_list('alias', flat=True)
|
.values_list('alias', flat=True)
|
||||||
for alias in items:
|
for alias in items:
|
||||||
|
@ -158,7 +163,7 @@ class RSForm(LibraryItem):
|
||||||
cst_type = guess_type(alias)
|
cst_type = guess_type(alias)
|
||||||
self._shift_positions(position, 1)
|
self._shift_positions(position, 1)
|
||||||
result = Constituenta.objects.create(
|
result = Constituenta.objects.create(
|
||||||
schema=self,
|
schema=self.model,
|
||||||
order=position,
|
order=position,
|
||||||
alias=alias,
|
alias=alias,
|
||||||
cst_type=cst_type,
|
cst_type=cst_type,
|
||||||
|
@ -191,7 +196,7 @@ class RSForm(LibraryItem):
|
||||||
result = deepcopy(items)
|
result = deepcopy(items)
|
||||||
for cst in result:
|
for cst in result:
|
||||||
cst.pk = None
|
cst.pk = None
|
||||||
cst.schema = self
|
cst.schema = self.model
|
||||||
cst.order = position
|
cst.order = position
|
||||||
cst.alias = mapping[cst.alias]
|
cst.alias = mapping[cst.alias]
|
||||||
cst.apply_mapping(mapping)
|
cst.apply_mapping(mapping)
|
||||||
|
@ -304,7 +309,7 @@ class RSForm(LibraryItem):
|
||||||
def create_version(self, version: str, description: str, data) -> Version:
|
def create_version(self, version: str, description: str, data) -> Version:
|
||||||
''' Creates version for current state. '''
|
''' Creates version for current state. '''
|
||||||
return Version.objects.create(
|
return Version.objects.create(
|
||||||
item=self,
|
item=self.model,
|
||||||
version=version,
|
version=version,
|
||||||
description=description,
|
description=description,
|
||||||
data=data
|
data=data
|
||||||
|
@ -330,7 +335,7 @@ class RSForm(LibraryItem):
|
||||||
prefix = get_type_prefix(cst_type)
|
prefix = get_type_prefix(cst_type)
|
||||||
for text in expressions:
|
for text in expressions:
|
||||||
new_item = Constituenta.objects.create(
|
new_item = Constituenta.objects.create(
|
||||||
schema=self,
|
schema=self.model,
|
||||||
order=position,
|
order=position,
|
||||||
alias=f'{prefix}{free_index}',
|
alias=f'{prefix}{free_index}',
|
||||||
definition_formal=text,
|
definition_formal=text,
|
||||||
|
@ -349,7 +354,7 @@ class RSForm(LibraryItem):
|
||||||
update_list = \
|
update_list = \
|
||||||
Constituenta.objects \
|
Constituenta.objects \
|
||||||
.only('id', 'order', 'schema') \
|
.only('id', 'order', 'schema') \
|
||||||
.filter(schema=self.pk, order__gte=start)
|
.filter(schema=self.model, order__gte=start)
|
||||||
for cst in update_list:
|
for cst in update_list:
|
||||||
cst.order += shift
|
cst.order += shift
|
||||||
Constituenta.objects.bulk_update(update_list, ['order'])
|
Constituenta.objects.bulk_update(update_list, ['order'])
|
||||||
|
|
|
@ -1,16 +1,4 @@
|
||||||
''' Django: Models. '''
|
''' Django: Models. '''
|
||||||
|
|
||||||
|
from .Constituenta import Constituenta, CstType
|
||||||
from .RSForm import RSForm
|
from .RSForm import RSForm
|
||||||
from .Constituenta import Constituenta, CstType, _empty_forms
|
|
||||||
from .Editor import Editor
|
|
||||||
from .LibraryItem import (
|
|
||||||
AccessPolicy,
|
|
||||||
LibraryItem,
|
|
||||||
LibraryItemType,
|
|
||||||
LocationHead,
|
|
||||||
User,
|
|
||||||
validate_location
|
|
||||||
)
|
|
||||||
from .LibraryTemplate import LibraryTemplate
|
|
||||||
from .Subscription import Subscription
|
|
||||||
from .Version import Version
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
''' REST API: Serializers. '''
|
''' REST API: Serializers. '''
|
||||||
|
|
||||||
from .basics import (
|
from .basics import (
|
||||||
AccessPolicySerializer,
|
|
||||||
ASTNodeSerializer,
|
ASTNodeSerializer,
|
||||||
ExpressionParseSerializer,
|
ExpressionParseSerializer,
|
||||||
ExpressionSerializer,
|
ExpressionSerializer,
|
||||||
LocationSerializer,
|
|
||||||
MultiFormSerializer,
|
MultiFormSerializer,
|
||||||
ResolverSerializer,
|
ResolverSerializer,
|
||||||
TextSerializer,
|
TextSerializer,
|
||||||
|
@ -20,22 +18,9 @@ from .data_access import (
|
||||||
CstSubstituteSerializer,
|
CstSubstituteSerializer,
|
||||||
CstTargetSerializer,
|
CstTargetSerializer,
|
||||||
InlineSynthesisSerializer,
|
InlineSynthesisSerializer,
|
||||||
LibraryItemBaseSerializer,
|
|
||||||
LibraryItemCloneSerializer,
|
|
||||||
LibraryItemDetailsSerializer,
|
|
||||||
LibraryItemSerializer,
|
|
||||||
RSFormParseSerializer,
|
RSFormParseSerializer,
|
||||||
RSFormSerializer,
|
RSFormSerializer
|
||||||
UsersListSerializer,
|
|
||||||
UserTargetSerializer,
|
|
||||||
VersionCreateSerializer,
|
|
||||||
VersionSerializer
|
|
||||||
)
|
)
|
||||||
from .io_files import FileSerializer, RSFormTRSSerializer, RSFormUploadSerializer
|
from .io_files import FileSerializer, RSFormTRSSerializer, RSFormUploadSerializer
|
||||||
from .io_pyconcept import PyConceptAdapter
|
from .io_pyconcept import PyConceptAdapter
|
||||||
from .schema_typing import (
|
from .responses import NewCstResponse, NewMultiCstResponse, ResultTextResponse
|
||||||
NewCstResponse,
|
|
||||||
NewMultiCstResponse,
|
|
||||||
NewVersionResponse,
|
|
||||||
ResultTextResponse
|
|
||||||
)
|
|
||||||
|
|
|
@ -4,10 +4,6 @@ from typing import cast
|
||||||
from cctext import EntityReference, Reference, ReferenceType, Resolver, SyntacticReference
|
from cctext import EntityReference, Reference, ReferenceType, Resolver, SyntacticReference
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from shared import messages as msg
|
|
||||||
|
|
||||||
from ..models import AccessPolicy, validate_location
|
|
||||||
|
|
||||||
|
|
||||||
class ExpressionSerializer(serializers.Serializer):
|
class ExpressionSerializer(serializers.Serializer):
|
||||||
''' Serializer: RSLang expression. '''
|
''' Serializer: RSLang expression. '''
|
||||||
|
@ -20,32 +16,6 @@ class WordFormSerializer(serializers.Serializer):
|
||||||
grams = serializers.CharField()
|
grams = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
class LocationSerializer(serializers.Serializer):
|
|
||||||
''' Serializer: Item location. '''
|
|
||||||
location = serializers.CharField(max_length=500)
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
attrs = super().validate(attrs)
|
|
||||||
if not validate_location(attrs['location']):
|
|
||||||
raise serializers.ValidationError({
|
|
||||||
'location': msg.invalidLocation()
|
|
||||||
})
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class AccessPolicySerializer(serializers.Serializer):
|
|
||||||
''' Serializer: Constituenta renaming. '''
|
|
||||||
access_policy = serializers.CharField()
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
attrs = super().validate(attrs)
|
|
||||||
if not attrs['access_policy'] in AccessPolicy.values:
|
|
||||||
raise serializers.ValidationError({
|
|
||||||
'access_policy': msg.invalidEnum(attrs['access_policy'])
|
|
||||||
})
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class MultiFormSerializer(serializers.Serializer):
|
class MultiFormSerializer(serializers.Serializer):
|
||||||
''' Serializer: inflect request. '''
|
''' Serializer: inflect request. '''
|
||||||
items = serializers.ListField(
|
items = serializers.ListField(
|
||||||
|
|
|
@ -7,89 +7,15 @@ from django.db import transaction
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
||||||
|
|
||||||
|
from apps.library.models import LibraryItem
|
||||||
|
from apps.library.serializers import LibraryItemBaseSerializer, LibraryItemDetailsSerializer
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
|
|
||||||
from ..models import Constituenta, CstType, LibraryItem, RSForm, Version
|
from ..models import Constituenta, CstType, RSForm
|
||||||
from .basics import CstParseSerializer
|
from .basics import CstParseSerializer
|
||||||
from .io_pyconcept import PyConceptAdapter
|
from .io_pyconcept import PyConceptAdapter
|
||||||
|
|
||||||
|
|
||||||
class LibraryItemBaseSerializer(serializers.ModelSerializer):
|
|
||||||
''' Serializer: LibraryItem entry full access. '''
|
|
||||||
class Meta:
|
|
||||||
''' serializer metadata. '''
|
|
||||||
model = LibraryItem
|
|
||||||
fields = '__all__'
|
|
||||||
read_only_fields = ('id',)
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryItemSerializer(serializers.ModelSerializer):
|
|
||||||
''' Serializer: LibraryItem entry limited access. '''
|
|
||||||
class Meta:
|
|
||||||
''' serializer metadata. '''
|
|
||||||
model = LibraryItem
|
|
||||||
fields = '__all__'
|
|
||||||
read_only_fields = ('id', 'item_type', 'owner', 'location', 'access_policy')
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryItemCloneSerializer(serializers.ModelSerializer):
|
|
||||||
''' Serializer: LibraryItem cloning. '''
|
|
||||||
items = PKField(many=True, required=False, queryset=Constituenta.objects.all())
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
''' serializer metadata. '''
|
|
||||||
model = LibraryItem
|
|
||||||
exclude = ['id', 'item_type', 'owner']
|
|
||||||
|
|
||||||
|
|
||||||
class VersionSerializer(serializers.ModelSerializer):
|
|
||||||
''' Serializer: Version data. '''
|
|
||||||
class Meta:
|
|
||||||
''' serializer metadata. '''
|
|
||||||
model = Version
|
|
||||||
fields = 'id', 'version', 'item', 'description', 'time_create'
|
|
||||||
read_only_fields = ('id', 'item', 'time_create')
|
|
||||||
|
|
||||||
|
|
||||||
class VersionInnerSerializer(serializers.ModelSerializer):
|
|
||||||
''' Serializer: Version data for list of versions. '''
|
|
||||||
class Meta:
|
|
||||||
''' serializer metadata. '''
|
|
||||||
model = Version
|
|
||||||
fields = 'id', 'version', 'description', 'time_create'
|
|
||||||
read_only_fields = ('id', 'item', 'time_create')
|
|
||||||
|
|
||||||
|
|
||||||
class VersionCreateSerializer(serializers.ModelSerializer):
|
|
||||||
''' Serializer: Version create data. '''
|
|
||||||
class Meta:
|
|
||||||
''' serializer metadata. '''
|
|
||||||
model = Version
|
|
||||||
fields = 'version', 'description'
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
|
||||||
''' Serializer: LibraryItem detailed data. '''
|
|
||||||
subscribers = serializers.SerializerMethodField()
|
|
||||||
editors = serializers.SerializerMethodField()
|
|
||||||
versions = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
''' serializer metadata. '''
|
|
||||||
model = LibraryItem
|
|
||||||
fields = '__all__'
|
|
||||||
read_only_fields = ('owner', 'id', 'item_type')
|
|
||||||
|
|
||||||
def get_subscribers(self, instance: LibraryItem) -> list[int]:
|
|
||||||
return [item.pk for item in instance.subscribers()]
|
|
||||||
|
|
||||||
def get_editors(self, instance: LibraryItem) -> list[int]:
|
|
||||||
return [item.pk for item in instance.editors()]
|
|
||||||
|
|
||||||
def get_versions(self, instance: LibraryItem) -> list:
|
|
||||||
return [VersionInnerSerializer(item).data for item in instance.versions()]
|
|
||||||
|
|
||||||
|
|
||||||
class CstBaseSerializer(serializers.ModelSerializer):
|
class CstBaseSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: Constituenta all data. '''
|
''' Serializer: Constituenta all data. '''
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -112,18 +38,19 @@ class CstSerializer(serializers.ModelSerializer):
|
||||||
definition: Optional[str] = data['definition_raw'] if 'definition_raw' in data else None
|
definition: Optional[str] = data['definition_raw'] if 'definition_raw' in data else None
|
||||||
term: Optional[str] = data['term_raw'] if 'term_raw' in data else None
|
term: Optional[str] = data['term_raw'] if 'term_raw' in data else None
|
||||||
term_changed = 'term_forms' in data
|
term_changed = 'term_forms' in data
|
||||||
|
schema = RSForm(instance.schema)
|
||||||
if definition is not None and definition != instance.definition_raw:
|
if definition is not None and definition != instance.definition_raw:
|
||||||
data['definition_resolved'] = instance.schema.resolver().resolve(definition)
|
data['definition_resolved'] = schema.resolver().resolve(definition)
|
||||||
if term is not None and term != instance.term_raw:
|
if term is not None and term != instance.term_raw:
|
||||||
data['term_resolved'] = instance.schema.resolver().resolve(term)
|
data['term_resolved'] = schema.resolver().resolve(term)
|
||||||
if data['term_resolved'] != instance.term_resolved and 'term_forms' not in data:
|
if data['term_resolved'] != instance.term_resolved and 'term_forms' not in data:
|
||||||
data['term_forms'] = []
|
data['term_forms'] = []
|
||||||
term_changed = data['term_resolved'] != instance.term_resolved
|
term_changed = data['term_resolved'] != instance.term_resolved
|
||||||
result: Constituenta = super().update(instance, data)
|
result: Constituenta = super().update(instance, data)
|
||||||
if term_changed:
|
if term_changed:
|
||||||
instance.schema.on_term_change([result.id])
|
schema.on_term_change([result.id])
|
||||||
result.refresh_from_db()
|
result.refresh_from_db()
|
||||||
instance.schema.save()
|
schema.save()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -169,16 +96,16 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
model = LibraryItem
|
model = LibraryItem
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def to_representation(self, instance: RSForm) -> dict:
|
def to_representation(self, instance: LibraryItem) -> dict:
|
||||||
result = LibraryItemDetailsSerializer(instance).data
|
result = LibraryItemDetailsSerializer(instance).data
|
||||||
result['items'] = []
|
result['items'] = []
|
||||||
for cst in instance.constituents().order_by('order'):
|
for cst in RSForm(instance).constituents().order_by('order'):
|
||||||
result['items'].append(CstSerializer(cst).data)
|
result['items'].append(CstSerializer(cst).data)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def to_versioned_data(self) -> dict:
|
def to_versioned_data(self) -> dict:
|
||||||
''' Create serializable version representation without redundant data. '''
|
''' Create serializable version representation without redundant data. '''
|
||||||
result = self.to_representation(cast(RSForm, self.instance))
|
result = self.to_representation(cast(LibraryItem, self.instance))
|
||||||
del result['versions']
|
del result['versions']
|
||||||
del result['subscribers']
|
del result['subscribers']
|
||||||
del result['editors']
|
del result['editors']
|
||||||
|
@ -195,14 +122,14 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def from_versioned_data(self, version: int, data: dict) -> dict:
|
def from_versioned_data(self, version: int, data: dict) -> dict:
|
||||||
''' Load data from version. '''
|
''' Load data from version. '''
|
||||||
result = self.to_representation(cast(RSForm, self.instance))
|
result = self.to_representation(cast(LibraryItem, self.instance))
|
||||||
result['version'] = version
|
result['version'] = version
|
||||||
return result | data
|
return result | data
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def restore_from_version(self, data: dict):
|
def restore_from_version(self, data: dict):
|
||||||
''' Load data from version. '''
|
''' Load data from version. '''
|
||||||
schema = cast(RSForm, self.instance)
|
schema = RSForm(cast(LibraryItem, self.instance))
|
||||||
items: list[dict] = data['items']
|
items: list[dict] = data['items']
|
||||||
ids: list[int] = [item['id'] for item in items]
|
ids: list[int] = [item['id'] for item in items]
|
||||||
processed: list[int] = []
|
processed: list[int] = []
|
||||||
|
@ -256,13 +183,13 @@ class RSFormParseSerializer(serializers.ModelSerializer):
|
||||||
model = LibraryItem
|
model = LibraryItem
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def to_representation(self, instance: RSForm):
|
def to_representation(self, instance: LibraryItem):
|
||||||
result = RSFormSerializer(instance).data
|
result = RSFormSerializer(instance).data
|
||||||
return self._parse_data(result)
|
return self._parse_data(result)
|
||||||
|
|
||||||
def from_versioned_data(self, version: int, data: dict) -> dict:
|
def from_versioned_data(self, version: int, data: dict) -> dict:
|
||||||
''' Load data from version and parse. '''
|
''' Load data from version and parse. '''
|
||||||
item = cast(RSForm, self.instance)
|
item = cast(LibraryItem, self.instance)
|
||||||
result = RSFormSerializer(item).from_versioned_data(version, data)
|
result = RSFormSerializer(item).from_versioned_data(version, data)
|
||||||
return self._parse_data(result)
|
return self._parse_data(result)
|
||||||
|
|
||||||
|
@ -281,7 +208,7 @@ class CstTargetSerializer(serializers.Serializer):
|
||||||
target = PKField(many=False, queryset=Constituenta.objects.all())
|
target = PKField(many=False, queryset=Constituenta.objects.all())
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
schema = cast(RSForm, self.context['schema'])
|
schema = cast(LibraryItem, self.context['schema'])
|
||||||
cst = cast(Constituenta, attrs['target'])
|
cst = cast(Constituenta, attrs['target'])
|
||||||
if schema and cst.schema != schema:
|
if schema and cst.schema != schema:
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
|
@ -295,16 +222,6 @@ class CstTargetSerializer(serializers.Serializer):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class UserTargetSerializer(serializers.Serializer):
|
|
||||||
''' Serializer: Target single User. '''
|
|
||||||
user = PKField(many=False, queryset=User.objects.all())
|
|
||||||
|
|
||||||
|
|
||||||
class UsersListSerializer(serializers.Serializer):
|
|
||||||
''' Serializer: List of Users. '''
|
|
||||||
users = PKField(many=True, queryset=User.objects.all())
|
|
||||||
|
|
||||||
|
|
||||||
class CstRenameSerializer(serializers.Serializer):
|
class CstRenameSerializer(serializers.Serializer):
|
||||||
''' Serializer: Constituenta renaming. '''
|
''' Serializer: Constituenta renaming. '''
|
||||||
target = PKField(many=False, queryset=Constituenta.objects.all())
|
target = PKField(many=False, queryset=Constituenta.objects.all())
|
||||||
|
@ -313,7 +230,7 @@ class CstRenameSerializer(serializers.Serializer):
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
attrs = super().validate(attrs)
|
attrs = super().validate(attrs)
|
||||||
schema = cast(RSForm, self.context['schema'])
|
schema = cast(LibraryItem, self.context['schema'])
|
||||||
cst = cast(Constituenta, attrs['target'])
|
cst = cast(Constituenta, attrs['target'])
|
||||||
if cst.schema != schema:
|
if cst.schema != schema:
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
|
@ -324,7 +241,7 @@ class CstRenameSerializer(serializers.Serializer):
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
'alias': msg.renameTrivial(new_alias)
|
'alias': msg.renameTrivial(new_alias)
|
||||||
})
|
})
|
||||||
if schema.constituents().filter(alias=new_alias).exists():
|
if RSForm(schema).constituents().filter(alias=new_alias).exists():
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
'alias': msg.aliasTaken(new_alias)
|
'alias': msg.aliasTaken(new_alias)
|
||||||
})
|
})
|
||||||
|
@ -336,7 +253,7 @@ class CstListSerializer(serializers.Serializer):
|
||||||
items = PKField(many=True, queryset=Constituenta.objects.all())
|
items = PKField(many=True, queryset=Constituenta.objects.all())
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
schema = cast(RSForm, self.context['schema'])
|
schema = cast(LibraryItem, self.context['schema'])
|
||||||
if not schema:
|
if not schema:
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
@ -368,7 +285,7 @@ class CstSubstituteSerializer(serializers.Serializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
schema = cast(RSForm, self.context['schema'])
|
schema = cast(LibraryItem, self.context['schema'])
|
||||||
deleted = set()
|
deleted = set()
|
||||||
for item in attrs['substitutions']:
|
for item in attrs['substitutions']:
|
||||||
original_cst = cast(Constituenta, item['original'])
|
original_cst = cast(Constituenta, item['original'])
|
||||||
|
@ -395,8 +312,8 @@ class CstSubstituteSerializer(serializers.Serializer):
|
||||||
|
|
||||||
class InlineSynthesisSerializer(serializers.Serializer):
|
class InlineSynthesisSerializer(serializers.Serializer):
|
||||||
''' Serializer: Inline synthesis operation input. '''
|
''' Serializer: Inline synthesis operation input. '''
|
||||||
receiver = PKField(many=False, queryset=RSForm.objects.all())
|
receiver = PKField(many=False, queryset=LibraryItem.objects.all())
|
||||||
source = PKField(many=False, queryset=RSForm.objects.all()) # type: ignore
|
source = PKField(many=False, queryset=LibraryItem.objects.all()) # type: ignore
|
||||||
items = PKField(many=True, queryset=Constituenta.objects.all())
|
items = PKField(many=True, queryset=Constituenta.objects.all())
|
||||||
substitutions = serializers.ListField(
|
substitutions = serializers.ListField(
|
||||||
child=CstSubstituteSerializerBase()
|
child=CstSubstituteSerializerBase()
|
||||||
|
@ -404,8 +321,8 @@ class InlineSynthesisSerializer(serializers.Serializer):
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
user = cast(User, self.context['user'])
|
user = cast(User, self.context['user'])
|
||||||
schema_in = cast(RSForm, attrs['source'])
|
schema_in = cast(LibraryItem, attrs['source'])
|
||||||
schema_out = cast(RSForm, attrs['receiver'])
|
schema_out = cast(LibraryItem, attrs['receiver'])
|
||||||
if user.is_anonymous or (schema_out.owner != user and not user.is_staff):
|
if user.is_anonymous or (schema_out.owner != user and not user.is_staff):
|
||||||
raise PermissionDenied({
|
raise PermissionDenied({
|
||||||
'message': msg.schemaNotOwned(),
|
'message': msg.schemaNotOwned(),
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from apps.library.models import LibraryItem
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
|
|
||||||
from ..models import Constituenta, RSForm
|
from ..models import Constituenta, RSForm
|
||||||
|
@ -29,14 +30,14 @@ class RSFormTRSSerializer(serializers.Serializer):
|
||||||
''' Serializer: TRS file production and loading for RSForm. '''
|
''' Serializer: TRS file production and loading for RSForm. '''
|
||||||
|
|
||||||
def to_representation(self, instance: RSForm) -> dict:
|
def to_representation(self, instance: RSForm) -> dict:
|
||||||
result = self._prepare_json_rsform(instance)
|
result = self._prepare_json_rsform(instance.model)
|
||||||
items = instance.constituents().order_by('order')
|
items = instance.constituents().order_by('order')
|
||||||
for cst in items:
|
for cst in items:
|
||||||
result['items'].append(self._prepare_json_constituenta(cst))
|
result['items'].append(self._prepare_json_constituenta(cst))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _prepare_json_rsform(schema: RSForm) -> dict:
|
def _prepare_json_rsform(schema: LibraryItem) -> dict:
|
||||||
return {
|
return {
|
||||||
'type': _TRS_TYPE,
|
'type': _TRS_TYPE,
|
||||||
'title': schema.title,
|
'title': schema.title,
|
||||||
|
@ -125,7 +126,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
||||||
result['comment'] = data.get('comment', '')
|
result['comment'] = data.get('comment', '')
|
||||||
if 'id' in data:
|
if 'id' in data:
|
||||||
result['id'] = data['id']
|
result['id'] = data['id']
|
||||||
self.instance = RSForm.objects.get(pk=result['id'])
|
self.instance = RSForm.from_id(result['id'])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def validate(self, attrs: dict):
|
def validate(self, attrs: dict):
|
||||||
|
@ -139,7 +140,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(self, validated_data: dict) -> RSForm:
|
def create(self, validated_data: dict) -> RSForm:
|
||||||
self.instance: RSForm = RSForm.objects.create(
|
self.instance: RSForm = RSForm.create(
|
||||||
owner=validated_data.get('owner', None),
|
owner=validated_data.get('owner', None),
|
||||||
alias=validated_data['alias'],
|
alias=validated_data['alias'],
|
||||||
title=validated_data['title'],
|
title=validated_data['title'],
|
||||||
|
@ -154,7 +155,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
||||||
for cst_data in validated_data['items']:
|
for cst_data in validated_data['items']:
|
||||||
cst = Constituenta(
|
cst = Constituenta(
|
||||||
alias=cst_data['alias'],
|
alias=cst_data['alias'],
|
||||||
schema=self.instance,
|
schema=self.instance.model,
|
||||||
order=order,
|
order=order,
|
||||||
cst_type=cst_data['cstType'],
|
cst_type=cst_data['cstType'],
|
||||||
)
|
)
|
||||||
|
@ -167,11 +168,11 @@ class RSFormTRSSerializer(serializers.Serializer):
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def update(self, instance: RSForm, validated_data) -> RSForm:
|
def update(self, instance: RSForm, validated_data) -> RSForm:
|
||||||
if 'alias' in validated_data:
|
if 'alias' in validated_data:
|
||||||
instance.alias = validated_data['alias']
|
instance.model.alias = validated_data['alias']
|
||||||
if 'title' in validated_data:
|
if 'title' in validated_data:
|
||||||
instance.title = validated_data['title']
|
instance.model.title = validated_data['title']
|
||||||
if 'comment' in validated_data:
|
if 'comment' in validated_data:
|
||||||
instance.comment = validated_data['comment']
|
instance.model.comment = validated_data['comment']
|
||||||
|
|
||||||
order = 1
|
order = 1
|
||||||
prev_constituents = instance.constituents()
|
prev_constituents = instance.constituents()
|
||||||
|
@ -188,7 +189,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
||||||
else:
|
else:
|
||||||
cst = Constituenta(
|
cst = Constituenta(
|
||||||
alias=cst_data['alias'],
|
alias=cst_data['alias'],
|
||||||
schema=instance,
|
schema=instance.model,
|
||||||
order=order,
|
order=order,
|
||||||
cst_type=cst_data['cstType'],
|
cst_type=cst_data['cstType'],
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,9 +21,3 @@ class NewMultiCstResponse(serializers.Serializer):
|
||||||
child=serializers.IntegerField()
|
child=serializers.IntegerField()
|
||||||
)
|
)
|
||||||
schema = RSFormParseSerializer()
|
schema = RSFormParseSerializer()
|
||||||
|
|
||||||
|
|
||||||
class NewVersionResponse(serializers.Serializer):
|
|
||||||
''' Serializer: Create cst response. '''
|
|
||||||
version = serializers.IntegerField()
|
|
||||||
schema = RSFormParseSerializer()
|
|
|
@ -1,6 +1,3 @@
|
||||||
''' Tests for Django Models. '''
|
''' Tests for Django Models. '''
|
||||||
from .t_Constituenta import *
|
from .t_Constituenta import *
|
||||||
from .t_Editor import *
|
|
||||||
from .t_LibraryItem import *
|
|
||||||
from .t_RSForm import *
|
from .t_RSForm import *
|
||||||
from .t_Subscription import *
|
|
||||||
|
|
|
@ -3,42 +3,42 @@ from django.db.utils import IntegrityError
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from apps.rsform.models import Constituenta, CstType, LibraryItemType, RSForm
|
from apps.rsform.models import Constituenta, CstType, RSForm
|
||||||
|
|
||||||
|
|
||||||
class TestConstituenta(TestCase):
|
class TestConstituenta(TestCase):
|
||||||
''' Testing Constituenta model. '''
|
''' Testing Constituenta model. '''
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.schema1 = RSForm.objects.create(title='Test1')
|
self.schema1 = RSForm.create(title='Test1')
|
||||||
self.schema2 = RSForm.objects.create(title='Test2')
|
self.schema2 = RSForm.create(title='Test2')
|
||||||
|
|
||||||
|
|
||||||
def test_str(self):
|
def test_str(self):
|
||||||
testStr = 'X1'
|
testStr = 'X1'
|
||||||
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1, order=1, convention='Test')
|
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1.model, order=1, convention='Test')
|
||||||
self.assertEqual(str(cst), testStr)
|
self.assertEqual(str(cst), testStr)
|
||||||
|
|
||||||
|
|
||||||
def test_url(self):
|
def test_url(self):
|
||||||
testStr = 'X1'
|
testStr = 'X1'
|
||||||
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1, order=1, convention='Test')
|
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1.model, order=1, convention='Test')
|
||||||
self.assertEqual(cst.get_absolute_url(), f'/api/constituents/{cst.pk}')
|
self.assertEqual(cst.get_absolute_url(), f'/api/constituents/{cst.pk}')
|
||||||
|
|
||||||
|
|
||||||
def test_order_not_null(self):
|
def test_order_not_null(self):
|
||||||
with self.assertRaises(IntegrityError):
|
with self.assertRaises(IntegrityError):
|
||||||
Constituenta.objects.create(alias='X1', schema=self.schema1)
|
Constituenta.objects.create(alias='X1', schema=self.schema1.model)
|
||||||
|
|
||||||
|
|
||||||
def test_order_positive_integer(self):
|
def test_order_positive_integer(self):
|
||||||
with self.assertRaises(IntegrityError):
|
with self.assertRaises(IntegrityError):
|
||||||
Constituenta.objects.create(alias='X1', schema=self.schema1, order=-1)
|
Constituenta.objects.create(alias='X1', schema=self.schema1.model, order=-1)
|
||||||
|
|
||||||
|
|
||||||
def test_order_min_value(self):
|
def test_order_min_value(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
cst = Constituenta.objects.create(alias='X1', schema=self.schema1, order=0)
|
cst = Constituenta.objects.create(alias='X1', schema=self.schema1.model, order=0)
|
||||||
cst.full_clean()
|
cst.full_clean()
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,10 +50,10 @@ class TestConstituenta(TestCase):
|
||||||
def test_create_default(self):
|
def test_create_default(self):
|
||||||
cst = Constituenta.objects.create(
|
cst = Constituenta.objects.create(
|
||||||
alias='X1',
|
alias='X1',
|
||||||
schema=self.schema1,
|
schema=self.schema1.model,
|
||||||
order=1
|
order=1
|
||||||
)
|
)
|
||||||
self.assertEqual(cst.schema, self.schema1)
|
self.assertEqual(cst.schema, self.schema1.model)
|
||||||
self.assertEqual(cst.order, 1)
|
self.assertEqual(cst.order, 1)
|
||||||
self.assertEqual(cst.alias, 'X1')
|
self.assertEqual(cst.alias, 'X1')
|
||||||
self.assertEqual(cst.cst_type, CstType.BASE)
|
self.assertEqual(cst.cst_type, CstType.BASE)
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from apps.rsform.models import Constituenta, CstType, RSForm, User
|
from apps.rsform.models import Constituenta, CstType, RSForm
|
||||||
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
class TestRSForm(TestCase):
|
class TestRSForm(TestCase):
|
||||||
|
@ -11,49 +12,49 @@ class TestRSForm(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user1 = User.objects.create(username='User1')
|
self.user1 = User.objects.create(username='User1')
|
||||||
self.user2 = User.objects.create(username='User2')
|
self.user2 = User.objects.create(username='User2')
|
||||||
self.schema = RSForm.objects.create(title='Test')
|
self.schema = RSForm.create(title='Test')
|
||||||
self.assertNotEqual(self.user1, self.user2)
|
self.assertNotEqual(self.user1, self.user2)
|
||||||
|
|
||||||
|
|
||||||
def test_constituents(self):
|
def test_constituents(self):
|
||||||
schema1 = RSForm.objects.create(title='Test1')
|
schema1 = RSForm.create(title='Test1')
|
||||||
schema2 = RSForm.objects.create(title='Test2')
|
schema2 = RSForm.create(title='Test2')
|
||||||
self.assertFalse(schema1.constituents().exists())
|
self.assertFalse(schema1.constituents().exists())
|
||||||
self.assertFalse(schema2.constituents().exists())
|
self.assertFalse(schema2.constituents().exists())
|
||||||
|
|
||||||
Constituenta.objects.create(alias='X1', schema=schema1, order=1)
|
Constituenta.objects.create(alias='X1', schema=schema1.model, order=1)
|
||||||
Constituenta.objects.create(alias='X2', schema=schema1, order=2)
|
Constituenta.objects.create(alias='X2', schema=schema1.model, order=2)
|
||||||
self.assertTrue(schema1.constituents().exists())
|
self.assertTrue(schema1.constituents().exists())
|
||||||
self.assertFalse(schema2.constituents().exists())
|
self.assertFalse(schema2.constituents().exists())
|
||||||
self.assertEqual(schema1.constituents().count(), 2)
|
self.assertEqual(schema1.constituents().count(), 2)
|
||||||
|
|
||||||
|
|
||||||
def test_get_max_index(self):
|
def test_get_max_index(self):
|
||||||
schema1 = RSForm.objects.create(title='Test1')
|
schema1 = RSForm.create(title='Test1')
|
||||||
Constituenta.objects.create(alias='X1', schema=schema1, order=1)
|
Constituenta.objects.create(alias='X1', schema=schema1.model, order=1)
|
||||||
Constituenta.objects.create(alias='D2', cst_type=CstType.TERM, schema=schema1, order=2)
|
Constituenta.objects.create(alias='D2', cst_type=CstType.TERM, schema=schema1.model, order=2)
|
||||||
self.assertEqual(schema1.get_max_index(CstType.BASE), 1)
|
self.assertEqual(schema1.get_max_index(CstType.BASE), 1)
|
||||||
self.assertEqual(schema1.get_max_index(CstType.TERM), 2)
|
self.assertEqual(schema1.get_max_index(CstType.TERM), 2)
|
||||||
self.assertEqual(schema1.get_max_index(CstType.AXIOM), 0)
|
self.assertEqual(schema1.get_max_index(CstType.AXIOM), 0)
|
||||||
|
|
||||||
|
|
||||||
def test_insert_at(self):
|
def test_insert_at(self):
|
||||||
schema = RSForm.objects.create(title='Test')
|
schema = RSForm.create(title='Test')
|
||||||
x1 = schema.insert_new('X1')
|
x1 = schema.insert_new('X1')
|
||||||
self.assertEqual(x1.order, 1)
|
self.assertEqual(x1.order, 1)
|
||||||
self.assertEqual(x1.schema, schema)
|
self.assertEqual(x1.schema, schema.model)
|
||||||
|
|
||||||
x2 = schema.insert_new('X2', position=1)
|
x2 = schema.insert_new('X2', position=1)
|
||||||
x1.refresh_from_db()
|
x1.refresh_from_db()
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
self.assertEqual(x2.schema, schema)
|
self.assertEqual(x2.schema, schema.model)
|
||||||
self.assertEqual(x1.order, 2)
|
self.assertEqual(x1.order, 2)
|
||||||
|
|
||||||
x3 = schema.insert_new('X3', position=4)
|
x3 = schema.insert_new('X3', position=4)
|
||||||
x2.refresh_from_db()
|
x2.refresh_from_db()
|
||||||
x1.refresh_from_db()
|
x1.refresh_from_db()
|
||||||
self.assertEqual(x3.order, 3)
|
self.assertEqual(x3.order, 3)
|
||||||
self.assertEqual(x3.schema, schema)
|
self.assertEqual(x3.schema, schema.model)
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
self.assertEqual(x1.order, 2)
|
self.assertEqual(x1.order, 2)
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ class TestRSForm(TestCase):
|
||||||
x2.refresh_from_db()
|
x2.refresh_from_db()
|
||||||
x1.refresh_from_db()
|
x1.refresh_from_db()
|
||||||
self.assertEqual(x4.order, 3)
|
self.assertEqual(x4.order, 3)
|
||||||
self.assertEqual(x4.schema, schema)
|
self.assertEqual(x4.schema, schema.model)
|
||||||
self.assertEqual(x3.order, 4)
|
self.assertEqual(x3.order, 4)
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
self.assertEqual(x1.order, 2)
|
self.assertEqual(x1.order, 2)
|
||||||
|
@ -94,11 +95,11 @@ class TestRSForm(TestCase):
|
||||||
def test_insert_last(self):
|
def test_insert_last(self):
|
||||||
x1 = self.schema.insert_new('X1')
|
x1 = self.schema.insert_new('X1')
|
||||||
self.assertEqual(x1.order, 1)
|
self.assertEqual(x1.order, 1)
|
||||||
self.assertEqual(x1.schema, self.schema)
|
self.assertEqual(x1.schema, self.schema.model)
|
||||||
|
|
||||||
x2 = self.schema.insert_new('X2')
|
x2 = self.schema.insert_new('X2')
|
||||||
self.assertEqual(x2.order, 2)
|
self.assertEqual(x2.order, 2)
|
||||||
self.assertEqual(x2.schema, self.schema)
|
self.assertEqual(x2.schema, self.schema.model)
|
||||||
self.assertEqual(x1.order, 1)
|
self.assertEqual(x1.order, 1)
|
||||||
|
|
||||||
def test_create_cst(self):
|
def test_create_cst(self):
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
''' Tests for REST API. '''
|
''' Tests for REST API. '''
|
||||||
from .t_library import *
|
from .t_cctext import *
|
||||||
from .t_constituents import *
|
from .t_constituents import *
|
||||||
from .t_operations import *
|
from .t_operations import *
|
||||||
from .t_rsforms import *
|
from .t_rsforms import *
|
||||||
from .t_versions import *
|
|
||||||
|
|
||||||
from .t_cctext import *
|
|
||||||
from .t_rslang import *
|
from .t_rslang import *
|
||||||
|
|
|
@ -8,12 +8,12 @@ class TestConstituentaAPI(EndpointTester):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.rsform_owned = RSForm.objects.create(title='Test', alias='T1', owner=self.user)
|
self.rsform_owned = RSForm.create(title='Test', alias='T1', owner=self.user)
|
||||||
self.rsform_unowned = RSForm.objects.create(title='Test2', alias='T2')
|
self.rsform_unowned = RSForm.create(title='Test2', alias='T2')
|
||||||
self.cst1 = Constituenta.objects.create(
|
self.cst1 = Constituenta.objects.create(
|
||||||
alias='X1',
|
alias='X1',
|
||||||
cst_type=CstType.BASE,
|
cst_type=CstType.BASE,
|
||||||
schema=self.rsform_owned,
|
schema=self.rsform_owned.model,
|
||||||
order=1,
|
order=1,
|
||||||
convention='Test',
|
convention='Test',
|
||||||
term_raw='Test1',
|
term_raw='Test1',
|
||||||
|
@ -22,7 +22,7 @@ class TestConstituentaAPI(EndpointTester):
|
||||||
self.cst2 = Constituenta.objects.create(
|
self.cst2 = Constituenta.objects.create(
|
||||||
alias='X2',
|
alias='X2',
|
||||||
cst_type=CstType.BASE,
|
cst_type=CstType.BASE,
|
||||||
schema=self.rsform_unowned,
|
schema=self.rsform_unowned.model,
|
||||||
order=1,
|
order=1,
|
||||||
convention='Test1',
|
convention='Test1',
|
||||||
term_raw='Test2',
|
term_raw='Test2',
|
||||||
|
@ -30,7 +30,7 @@ class TestConstituentaAPI(EndpointTester):
|
||||||
)
|
)
|
||||||
self.cst3 = Constituenta.objects.create(
|
self.cst3 = Constituenta.objects.create(
|
||||||
alias='X3',
|
alias='X3',
|
||||||
schema=self.rsform_owned,
|
schema=self.rsform_owned.model,
|
||||||
order=2,
|
order=2,
|
||||||
term_raw='Test3',
|
term_raw='Test3',
|
||||||
term_resolved='Test3',
|
term_resolved='Test3',
|
||||||
|
|
|
@ -10,16 +10,16 @@ class TestInlineSynthesis(EndpointTester):
|
||||||
@decl_endpoint('/api/operations/inline-synthesis', method='patch')
|
@decl_endpoint('/api/operations/inline-synthesis', method='patch')
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.schema1 = RSForm.objects.create(title='Test1', alias='T1', owner=self.user)
|
self.schema1 = RSForm.create(title='Test1', alias='T1', owner=self.user)
|
||||||
self.schema2 = RSForm.objects.create(title='Test2', alias='T2', owner=self.user)
|
self.schema2 = RSForm.create(title='Test2', alias='T2', owner=self.user)
|
||||||
self.unowned = RSForm.objects.create(title='Test3', alias='T3')
|
self.unowned = RSForm.create(title='Test3', alias='T3')
|
||||||
|
|
||||||
|
|
||||||
def test_inline_synthesis_inputs(self):
|
def test_inline_synthesis_inputs(self):
|
||||||
invalid_id = 1338
|
invalid_id = 1338
|
||||||
data = {
|
data = {
|
||||||
'receiver': self.unowned.pk,
|
'receiver': self.unowned.model.pk,
|
||||||
'source': self.schema1.pk,
|
'source': self.schema1.model.pk,
|
||||||
'items': [],
|
'items': [],
|
||||||
'substitutions': []
|
'substitutions': []
|
||||||
}
|
}
|
||||||
|
@ -28,11 +28,11 @@ class TestInlineSynthesis(EndpointTester):
|
||||||
data['receiver'] = invalid_id
|
data['receiver'] = invalid_id
|
||||||
self.executeBadData(data=data)
|
self.executeBadData(data=data)
|
||||||
|
|
||||||
data['receiver'] = self.schema1.pk
|
data['receiver'] = self.schema1.model.pk
|
||||||
data['source'] = invalid_id
|
data['source'] = invalid_id
|
||||||
self.executeBadData(data=data)
|
self.executeBadData(data=data)
|
||||||
|
|
||||||
data['source'] = self.schema1.pk
|
data['source'] = self.schema1.model.pk
|
||||||
self.executeOK(data=data)
|
self.executeOK(data=data)
|
||||||
|
|
||||||
data['items'] = [invalid_id]
|
data['items'] = [invalid_id]
|
||||||
|
@ -51,8 +51,8 @@ class TestInlineSynthesis(EndpointTester):
|
||||||
ks2_a1 = self.schema2.insert_new('A1', definition_formal='1=1') # -> not included in items
|
ks2_a1 = self.schema2.insert_new('A1', definition_formal='1=1') # -> not included in items
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'receiver': self.schema1.pk,
|
'receiver': self.schema1.model.pk,
|
||||||
'source': self.schema2.pk,
|
'source': self.schema2.model.pk,
|
||||||
'items': [ks2_x1.pk, ks2_x2.pk, ks2_s1.pk, ks2_d1.pk],
|
'items': [ks2_x1.pk, ks2_x2.pk, ks2_s1.pk, ks2_d1.pk],
|
||||||
'substitutions': [
|
'substitutions': [
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,15 +6,8 @@ from zipfile import ZipFile
|
||||||
from cctext import ReferenceType
|
from cctext import ReferenceType
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from apps.rsform.models import (
|
from apps.library.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead
|
||||||
AccessPolicy,
|
from apps.rsform.models import Constituenta, CstType, RSForm
|
||||||
Constituenta,
|
|
||||||
CstType,
|
|
||||||
LibraryItem,
|
|
||||||
LibraryItemType,
|
|
||||||
LocationHead,
|
|
||||||
RSForm
|
|
||||||
)
|
|
||||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||||
from shared.testing_utils import response_contains
|
from shared.testing_utils import response_contains
|
||||||
|
|
||||||
|
@ -24,12 +17,12 @@ class TestRSFormViewset(EndpointTester):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.owned = RSForm.objects.create(title='Test', alias='T1', owner=self.user)
|
self.owned = RSForm.create(title='Test', alias='T1', owner=self.user)
|
||||||
self.owned_id = self.owned.pk
|
self.owned_id = self.owned.model.pk
|
||||||
self.unowned = RSForm.objects.create(title='Test2', alias='T2')
|
self.unowned = RSForm.create(title='Test2', alias='T2')
|
||||||
self.unowned_id = self.unowned.pk
|
self.unowned_id = self.unowned.model.pk
|
||||||
self.private = RSForm.objects.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
self.private = RSForm.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
||||||
self.private_id = self.private.pk
|
self.private_id = self.private.model.pk
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/create-detailed', method='post')
|
@decl_endpoint('/api/rsforms/create-detailed', method='post')
|
||||||
|
@ -57,25 +50,25 @@ class TestRSFormViewset(EndpointTester):
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms', method='get')
|
@decl_endpoint('/api/rsforms', method='get')
|
||||||
def test_list_rsforms(self):
|
def test_list_rsforms(self):
|
||||||
non_schema = LibraryItem.objects.create(
|
oss = LibraryItem.objects.create(
|
||||||
item_type=LibraryItemType.OPERATION_SCHEMA,
|
item_type=LibraryItemType.OPERATION_SCHEMA,
|
||||||
title='Test3'
|
title='Test3'
|
||||||
)
|
)
|
||||||
response = self.executeOK()
|
response = self.executeOK()
|
||||||
self.assertFalse(response_contains(response, non_schema))
|
self.assertFalse(response_contains(response, oss))
|
||||||
self.assertTrue(response_contains(response, self.unowned))
|
self.assertTrue(response_contains(response, self.unowned.model))
|
||||||
self.assertTrue(response_contains(response, self.owned))
|
self.assertTrue(response_contains(response, self.owned.model))
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/contents', method='get')
|
@decl_endpoint('/api/rsforms/{item}/contents', method='get')
|
||||||
def test_contents(self):
|
def test_contents(self):
|
||||||
response = self.executeOK(item=self.owned_id)
|
response = self.executeOK(item=self.owned_id)
|
||||||
self.assertEqual(response.data['owner'], self.owned.owner.pk)
|
self.assertEqual(response.data['owner'], self.owned.model.owner.pk)
|
||||||
self.assertEqual(response.data['title'], self.owned.title)
|
self.assertEqual(response.data['title'], self.owned.model.title)
|
||||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
self.assertEqual(response.data['alias'], self.owned.model.alias)
|
||||||
self.assertEqual(response.data['location'], self.owned.location)
|
self.assertEqual(response.data['location'], self.owned.model.location)
|
||||||
self.assertEqual(response.data['access_policy'], self.owned.access_policy)
|
self.assertEqual(response.data['access_policy'], self.owned.model.access_policy)
|
||||||
self.assertEqual(response.data['visible'], self.owned.visible)
|
self.assertEqual(response.data['visible'], self.owned.model.visible)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/details', method='get')
|
@decl_endpoint('/api/rsforms/{item}/details', method='get')
|
||||||
|
@ -92,12 +85,12 @@ class TestRSFormViewset(EndpointTester):
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self.executeOK(item=self.owned_id)
|
response = self.executeOK(item=self.owned_id)
|
||||||
self.assertEqual(response.data['owner'], self.owned.owner.pk)
|
self.assertEqual(response.data['owner'], self.owned.model.owner.pk)
|
||||||
self.assertEqual(response.data['title'], self.owned.title)
|
self.assertEqual(response.data['title'], self.owned.model.title)
|
||||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
self.assertEqual(response.data['alias'], self.owned.model.alias)
|
||||||
self.assertEqual(response.data['location'], self.owned.location)
|
self.assertEqual(response.data['location'], self.owned.model.location)
|
||||||
self.assertEqual(response.data['access_policy'], self.owned.access_policy)
|
self.assertEqual(response.data['access_policy'], self.owned.model.access_policy)
|
||||||
self.assertEqual(response.data['visible'], self.owned.visible)
|
self.assertEqual(response.data['visible'], self.owned.model.visible)
|
||||||
|
|
||||||
self.assertEqual(len(response.data['items']), 2)
|
self.assertEqual(len(response.data['items']), 2)
|
||||||
self.assertEqual(response.data['items'][0]['id'], x1.pk)
|
self.assertEqual(response.data['items'][0]['id'], x1.pk)
|
||||||
|
@ -176,9 +169,9 @@ class TestRSFormViewset(EndpointTester):
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/export-trs', method='get')
|
@decl_endpoint('/api/rsforms/{item}/export-trs', method='get')
|
||||||
def test_export_trs(self):
|
def test_export_trs(self):
|
||||||
schema = RSForm.objects.create(title='Test')
|
schema = RSForm.create(title='Test')
|
||||||
schema.insert_new('X1')
|
schema.insert_new('X1')
|
||||||
response = self.executeOK(item=schema.pk)
|
response = self.executeOK(item=schema.model.pk)
|
||||||
self.assertEqual(response.headers['Content-Disposition'], 'attachment; filename=Schema.trs')
|
self.assertEqual(response.headers['Content-Disposition'], 'attachment; filename=Schema.trs')
|
||||||
with io.BytesIO(response.content) as stream:
|
with io.BytesIO(response.content) as stream:
|
||||||
with ZipFile(stream, 'r') as zipped_file:
|
with ZipFile(stream, 'r') as zipped_file:
|
||||||
|
@ -458,7 +451,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
@decl_endpoint('/api/rsforms/{item}/load-trs', method='patch')
|
@decl_endpoint('/api/rsforms/{item}/load-trs', method='patch')
|
||||||
def test_load_trs(self):
|
def test_load_trs(self):
|
||||||
self.set_params(item=self.owned_id)
|
self.set_params(item=self.owned_id)
|
||||||
self.owned.title = 'Test11'
|
self.owned.model.title = 'Test11'
|
||||||
self.owned.save()
|
self.owned.save()
|
||||||
x1 = self.owned.insert_new('X1')
|
x1 = self.owned.insert_new('X1')
|
||||||
work_dir = os.path.dirname(os.path.abspath(__file__))
|
work_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
@ -467,7 +460,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
response = self.client.patch(self.endpoint, data=data, format='multipart')
|
response = self.client.patch(self.endpoint, data=data, format='multipart')
|
||||||
self.owned.refresh_from_db()
|
self.owned.refresh_from_db()
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(self.owned.title, 'Test11')
|
self.assertEqual(self.owned.model.title, 'Test11')
|
||||||
self.assertEqual(len(response.data['items']), 25)
|
self.assertEqual(len(response.data['items']), 25)
|
||||||
self.assertEqual(self.owned.constituents().count(), 25)
|
self.assertEqual(self.owned.constituents().count(), 25)
|
||||||
self.assertFalse(Constituenta.objects.filter(pk=x1.pk).exists())
|
self.assertFalse(Constituenta.objects.filter(pk=x1.pk).exists())
|
||||||
|
|
|
@ -5,22 +5,14 @@ from rest_framework import routers
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
library_router = routers.SimpleRouter(trailing_slash=False)
|
library_router = routers.SimpleRouter(trailing_slash=False)
|
||||||
library_router.register('library', views.LibraryViewSet, 'Library')
|
|
||||||
library_router.register('rsforms', views.RSFormViewSet, 'RSForm')
|
library_router.register('rsforms', views.RSFormViewSet, 'RSForm')
|
||||||
library_router.register('versions', views.VersionViewset, 'Version')
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('library/active', views.LibraryActiveView.as_view()),
|
|
||||||
path('library/all', views.LibraryAdminView.as_view()),
|
|
||||||
path('library/templates', views.LibraryTemplatesView.as_view(), name='templates'),
|
|
||||||
path('constituents/<int:pk>', views.ConstituentAPIView.as_view(), name='constituenta-detail'),
|
path('constituents/<int:pk>', views.ConstituentAPIView.as_view(), name='constituenta-detail'),
|
||||||
path('rsforms/import-trs', views.TrsImportView.as_view()),
|
path('rsforms/import-trs', views.TrsImportView.as_view()),
|
||||||
path('rsforms/create-detailed', views.create_rsform),
|
path('rsforms/create-detailed', views.create_rsform),
|
||||||
|
|
||||||
path('versions/<int:pk>/export-file', views.export_file),
|
|
||||||
path('rsforms/<int:pk_item>/versions/create', views.create_version),
|
|
||||||
path('rsforms/<int:pk_item>/versions/<int:pk_version>', views.retrieve_version),
|
|
||||||
|
|
||||||
path('operations/inline-synthesis', views.inline_synthesis),
|
path('operations/inline-synthesis', views.inline_synthesis),
|
||||||
|
|
||||||
path('rslang/parse-expression', views.parse_expression),
|
path('rslang/parse-expression', views.parse_expression),
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
''' Utility functions '''
|
''' Utility functions '''
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
from io import BytesIO
|
|
||||||
from zipfile import ZipFile
|
|
||||||
|
|
||||||
# Name for JSON inside Exteor files archive
|
# Name for JSON inside Exteor files archive
|
||||||
EXTEOR_INNER_FILENAME = 'document.json'
|
EXTEOR_INNER_FILENAME = 'document.json'
|
||||||
|
@ -11,23 +8,6 @@ EXTEOR_INNER_FILENAME = 'document.json'
|
||||||
_REF_OLD_PATTERN = re.compile(r'@{([^0-9\-][^\}\|\{]*?)\|([^\}\|\{]*?)\|([^\}\|\{]*?)}')
|
_REF_OLD_PATTERN = re.compile(r'@{([^0-9\-][^\}\|\{]*?)\|([^\}\|\{]*?)\|([^\}\|\{]*?)}')
|
||||||
|
|
||||||
|
|
||||||
def read_zipped_json(data, json_filename: str) -> dict:
|
|
||||||
''' Read JSON from zipped data '''
|
|
||||||
with ZipFile(data, 'r') as archive:
|
|
||||||
json_data = archive.read(json_filename)
|
|
||||||
result: dict = json.loads(json_data)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def write_zipped_json(json_data: dict, json_filename: str) -> bytes:
|
|
||||||
''' Write json JSON to bytes buffer '''
|
|
||||||
content = BytesIO()
|
|
||||||
data = json.dumps(json_data, indent=4, ensure_ascii=False)
|
|
||||||
with ZipFile(content, 'w') as archive:
|
|
||||||
archive.writestr(json_filename, data=data)
|
|
||||||
return content.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
def apply_pattern(text: str, mapping: dict[str, str], pattern: re.Pattern[str]) -> str:
|
def apply_pattern(text: str, mapping: dict[str, str], pattern: re.Pattern[str]) -> str:
|
||||||
''' Apply mapping to matching in regular expression pattern subgroup 1 '''
|
''' Apply mapping to matching in regular expression pattern subgroup 1 '''
|
||||||
if text == '' or pattern == '':
|
if text == '' or pattern == '':
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
''' REST API: Endpoint processors. '''
|
''' REST API: Endpoint processors. '''
|
||||||
from .cctext import generate_lexeme, inflect, parse_text
|
from .cctext import generate_lexeme, inflect, parse_text
|
||||||
from .constituents import ConstituentAPIView
|
from .constituents import ConstituentAPIView
|
||||||
from .library import LibraryActiveView, LibraryAdminView, LibraryTemplatesView, LibraryViewSet
|
|
||||||
from .operations import inline_synthesis
|
from .operations import inline_synthesis
|
||||||
from .rsforms import RSFormViewSet, TrsImportView, create_rsform
|
from .rsforms import RSFormViewSet, TrsImportView, create_rsform
|
||||||
from .rslang import convert_to_ascii, convert_to_math, parse_expression
|
from .rslang import convert_to_ascii, convert_to_math, parse_expression
|
||||||
from .versions import VersionViewset, create_version, export_file, retrieve_version
|
|
||||||
|
|
|
@ -27,11 +27,11 @@ def inline_synthesis(request: Request):
|
||||||
)
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
schema = cast(m.RSForm, serializer.validated_data['receiver'])
|
receiver = m.RSForm(serializer.validated_data['receiver'])
|
||||||
items = cast(list[m.Constituenta], serializer.validated_data['items'])
|
items = cast(list[m.Constituenta], serializer.validated_data['items'])
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
new_items = schema.insert_copy(items)
|
new_items = receiver.insert_copy(items)
|
||||||
for substitution in serializer.validated_data['substitutions']:
|
for substitution in serializer.validated_data['substitutions']:
|
||||||
original = cast(m.Constituenta, substitution['original'])
|
original = cast(m.Constituenta, substitution['original'])
|
||||||
replacement = cast(m.Constituenta, substitution['substitution'])
|
replacement = cast(m.Constituenta, substitution['substitution'])
|
||||||
|
@ -41,10 +41,10 @@ def inline_synthesis(request: Request):
|
||||||
else:
|
else:
|
||||||
index = next(i for (i, cst) in enumerate(items) if cst == replacement)
|
index = next(i for (i, cst) in enumerate(items) if cst == replacement)
|
||||||
replacement = new_items[index]
|
replacement = new_items[index]
|
||||||
schema.substitute(original, replacement, substitution['transfer_term'])
|
receiver.substitute(original, replacement, substitution['transfer_term'])
|
||||||
schema.restore_order()
|
receiver.restore_order()
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.RSFormParseSerializer(schema).data
|
data=s.RSFormParseSerializer(receiver.model).data
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,8 +13,11 @@ from rest_framework.decorators import action, api_view
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from apps.library.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead
|
||||||
|
from apps.library.serializers import LibraryItemSerializer
|
||||||
|
from apps.users.models import User
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
from shared import permissions
|
from shared import permissions, utility
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
from .. import serializers as s
|
from .. import serializers as s
|
||||||
|
@ -25,11 +28,11 @@ from .. import utils
|
||||||
@extend_schema_view()
|
@extend_schema_view()
|
||||||
class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
||||||
''' Endpoint: RSForm operations. '''
|
''' Endpoint: RSForm operations. '''
|
||||||
queryset = m.RSForm.objects.all()
|
queryset = LibraryItem.objects.filter(item_type=LibraryItemType.RSFORM)
|
||||||
serializer_class = s.LibraryItemSerializer
|
serializer_class = LibraryItemSerializer
|
||||||
|
|
||||||
def _get_schema(self) -> m.RSForm:
|
def _get_item(self) -> LibraryItem:
|
||||||
return cast(m.RSForm, self.get_object())
|
return cast(LibraryItem, self.get_object())
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
''' Determine permission class. '''
|
''' Determine permission class. '''
|
||||||
|
@ -71,18 +74,18 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['post'], url_path='create-cst')
|
@action(detail=True, methods=['post'], url_path='create-cst')
|
||||||
def create_cst(self, request: Request, pk):
|
def create_cst(self, request: Request, pk):
|
||||||
''' Create new constituenta. '''
|
''' Create new constituenta. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_item()
|
||||||
serializer = s.CstCreateSerializer(data=request.data)
|
serializer = s.CstCreateSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
if 'insert_after' in data and data['insert_after'] is not None:
|
if 'insert_after' in data and data['insert_after'] is not None:
|
||||||
try:
|
try:
|
||||||
insert_after = m.Constituenta.objects.get(pk=data['insert_after'])
|
insert_after = m.Constituenta.objects.get(pk=data['insert_after'])
|
||||||
except m.LibraryItem.DoesNotExist:
|
except LibraryItem.DoesNotExist:
|
||||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||||
else:
|
else:
|
||||||
insert_after = None
|
insert_after = None
|
||||||
new_cst = schema.create_cst(data, insert_after)
|
new_cst = m.RSForm(schema).create_cst(data, insert_after)
|
||||||
|
|
||||||
schema.refresh_from_db()
|
schema.refresh_from_db()
|
||||||
response = Response(
|
response = Response(
|
||||||
|
@ -109,7 +112,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['patch'], url_path='produce-structure')
|
@action(detail=True, methods=['patch'], url_path='produce-structure')
|
||||||
def produce_structure(self, request: Request, pk):
|
def produce_structure(self, request: Request, pk):
|
||||||
''' Produce a term for every element of the target constituenta typification. '''
|
''' Produce a term for every element of the target constituenta typification. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_item()
|
||||||
|
|
||||||
serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema})
|
serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema})
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
@ -123,7 +126,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
data={f'{cst.id}': msg.constituentaNoStructure()}
|
data={f'{cst.id}': msg.constituentaNoStructure()}
|
||||||
)
|
)
|
||||||
|
|
||||||
result = schema.produce_structure(cst, cst_parse)
|
result = m.RSForm(schema).produce_structure(cst, cst_parse)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data={
|
data={
|
||||||
|
@ -146,7 +149,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['patch'], url_path='rename-cst')
|
@action(detail=True, methods=['patch'], url_path='rename-cst')
|
||||||
def rename_cst(self, request: Request, pk):
|
def rename_cst(self, request: Request, pk):
|
||||||
''' Rename constituenta possibly changing type. '''
|
''' Rename constituenta possibly changing type. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_item()
|
||||||
serializer = s.CstRenameSerializer(data=request.data, context={'schema': schema})
|
serializer = s.CstRenameSerializer(data=request.data, context={'schema': schema})
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
@ -158,10 +161,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
cst.save()
|
cst.save()
|
||||||
schema.apply_mapping(mapping={old_alias: cst.alias}, change_aliases=False)
|
m.RSForm(schema).apply_mapping(mapping={old_alias: cst.alias}, change_aliases=False)
|
||||||
|
|
||||||
schema.refresh_from_db()
|
schema.refresh_from_db()
|
||||||
cst.refresh_from_db()
|
cst.refresh_from_db()
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data={
|
data={
|
||||||
|
@ -184,7 +187,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['patch'], url_path='substitute')
|
@action(detail=True, methods=['patch'], url_path='substitute')
|
||||||
def substitute(self, request: Request, pk):
|
def substitute(self, request: Request, pk):
|
||||||
''' Substitute occurrences of constituenta with another one. '''
|
''' Substitute occurrences of constituenta with another one. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_item()
|
||||||
serializer = s.CstSubstituteSerializer(
|
serializer = s.CstSubstituteSerializer(
|
||||||
data=request.data,
|
data=request.data,
|
||||||
context={'schema': schema}
|
context={'schema': schema}
|
||||||
|
@ -195,7 +198,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
for substitution in serializer.validated_data['substitutions']:
|
for substitution in serializer.validated_data['substitutions']:
|
||||||
original = cast(m.Constituenta, substitution['original'])
|
original = cast(m.Constituenta, substitution['original'])
|
||||||
replacement = cast(m.Constituenta, substitution['substitution'])
|
replacement = cast(m.Constituenta, substitution['substitution'])
|
||||||
schema.substitute(original, replacement, substitution['transfer_term'])
|
m.RSForm(schema).substitute(original, replacement, substitution['transfer_term'])
|
||||||
|
|
||||||
schema.refresh_from_db()
|
schema.refresh_from_db()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
|
@ -216,13 +220,14 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['patch'], url_path='delete-multiple-cst')
|
@action(detail=True, methods=['patch'], url_path='delete-multiple-cst')
|
||||||
def delete_multiple_cst(self, request: Request, pk):
|
def delete_multiple_cst(self, request: Request, pk):
|
||||||
''' Endpoint: Delete multiple constituents. '''
|
''' Endpoint: Delete multiple constituents. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_item()
|
||||||
serializer = s.CstListSerializer(
|
serializer = s.CstListSerializer(
|
||||||
data=request.data,
|
data=request.data,
|
||||||
context={'schema': schema}
|
context={'schema': schema}
|
||||||
)
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema.delete_cst(serializer.validated_data['items'])
|
m.RSForm(schema).delete_cst(serializer.validated_data['items'])
|
||||||
|
|
||||||
schema.refresh_from_db()
|
schema.refresh_from_db()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
|
@ -243,13 +248,13 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['patch'], url_path='move-cst')
|
@action(detail=True, methods=['patch'], url_path='move-cst')
|
||||||
def move_cst(self, request: Request, pk):
|
def move_cst(self, request: Request, pk):
|
||||||
''' Endpoint: Move multiple constituents. '''
|
''' Endpoint: Move multiple constituents. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_item()
|
||||||
serializer = s.CstMoveSerializer(
|
serializer = s.CstMoveSerializer(
|
||||||
data=request.data,
|
data=request.data,
|
||||||
context={'schema': schema}
|
context={'schema': schema}
|
||||||
)
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema.move_cst(
|
m.RSForm(schema).move_cst(
|
||||||
listCst=serializer.validated_data['items'],
|
listCst=serializer.validated_data['items'],
|
||||||
target=serializer.validated_data['move_to']
|
target=serializer.validated_data['move_to']
|
||||||
)
|
)
|
||||||
|
@ -271,8 +276,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
||||||
def reset_aliases(self, request: Request, pk):
|
def reset_aliases(self, request: Request, pk):
|
||||||
''' Endpoint: Recreate all aliases based on order. '''
|
''' Endpoint: Recreate all aliases based on order. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_item()
|
||||||
schema.reset_aliases()
|
m.RSForm(schema).reset_aliases()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.RSFormParseSerializer(schema).data
|
data=s.RSFormParseSerializer(schema).data
|
||||||
|
@ -291,8 +296,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['patch'], url_path='restore-order')
|
@action(detail=True, methods=['patch'], url_path='restore-order')
|
||||||
def restore_order(self, request: Request, pk):
|
def restore_order(self, request: Request, pk):
|
||||||
''' Endpoint: Restore order based on types and term graph. '''
|
''' Endpoint: Restore order based on types and term graph. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_item()
|
||||||
schema.restore_order()
|
m.RSForm(schema).restore_order()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.RSFormParseSerializer(schema).data
|
data=s.RSFormParseSerializer(schema).data
|
||||||
|
@ -314,9 +319,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
''' Endpoint: Load data from file and replace current schema. '''
|
''' Endpoint: Load data from file and replace current schema. '''
|
||||||
input_serializer = s.RSFormUploadSerializer(data=request.data)
|
input_serializer = s.RSFormUploadSerializer(data=request.data)
|
||||||
input_serializer.is_valid(raise_exception=True)
|
input_serializer.is_valid(raise_exception=True)
|
||||||
schema = self._get_schema()
|
|
||||||
|
schema = self._get_item()
|
||||||
load_metadata = input_serializer.validated_data['load_metadata']
|
load_metadata = input_serializer.validated_data['load_metadata']
|
||||||
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
||||||
data['id'] = schema.pk
|
data['id'] = schema.pk
|
||||||
|
|
||||||
serializer = s.RSFormTRSSerializer(
|
serializer = s.RSFormTRSSerializer(
|
||||||
|
@ -324,10 +330,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
context={'load_meta': load_metadata}
|
context={'load_meta': load_metadata}
|
||||||
)
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
result = serializer.save()
|
result: m.RSForm = serializer.save()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.RSFormParseSerializer(result).data
|
data=s.RSFormParseSerializer(result.model).data
|
||||||
)
|
)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -342,10 +348,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['get'], url_path='contents')
|
@action(detail=True, methods=['get'], url_path='contents')
|
||||||
def contents(self, request: Request, pk):
|
def contents(self, request: Request, pk):
|
||||||
''' Endpoint: View schema db contents (including constituents). '''
|
''' Endpoint: View schema db contents (including constituents). '''
|
||||||
schema = s.RSFormSerializer(self.get_object())
|
serializer = s.RSFormSerializer(self.get_object())
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=schema.data
|
data=serializer.data
|
||||||
)
|
)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -360,7 +366,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['get'], url_path='details')
|
@action(detail=True, methods=['get'], url_path='details')
|
||||||
def details(self, request: Request, pk):
|
def details(self, request: Request, pk):
|
||||||
''' Endpoint: Detailed schema view including statuses and parse. '''
|
''' Endpoint: Detailed schema view including statuses and parse. '''
|
||||||
serializer = s.RSFormParseSerializer(self._get_schema())
|
serializer = s.RSFormParseSerializer(self.get_object())
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=serializer.data
|
data=serializer.data
|
||||||
|
@ -381,8 +387,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
serializer = s.ExpressionSerializer(data=request.data)
|
serializer = s.ExpressionSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
expression = serializer.validated_data['expression']
|
expression = serializer.validated_data['expression']
|
||||||
schema = s.PyConceptAdapter(self._get_schema())
|
pySchema = s.PyConceptAdapter(m.RSForm(self.get_object()))
|
||||||
result = pyconcept.check_expression(json.dumps(schema.data), expression)
|
result = pyconcept.check_expression(json.dumps(pySchema.data), expression)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=json.loads(result)
|
data=json.loads(result)
|
||||||
|
@ -403,7 +409,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
serializer = s.TextSerializer(data=request.data)
|
serializer = s.TextSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
text = serializer.validated_data['text']
|
text = serializer.validated_data['text']
|
||||||
resolver = self._get_schema().resolver()
|
resolver = m.RSForm(self.get_object()).resolver()
|
||||||
resolver.resolve(text)
|
resolver.resolve(text)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
|
@ -422,9 +428,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
@action(detail=True, methods=['get'], url_path='export-trs')
|
@action(detail=True, methods=['get'], url_path='export-trs')
|
||||||
def export_trs(self, request: Request, pk):
|
def export_trs(self, request: Request, pk):
|
||||||
''' Endpoint: Download Exteor compatible file. '''
|
''' Endpoint: Download Exteor compatible file. '''
|
||||||
data = s.RSFormTRSSerializer(self._get_schema()).data
|
schema = self._get_item()
|
||||||
file = utils.write_zipped_json(data, utils.EXTEOR_INNER_FILENAME)
|
data = s.RSFormTRSSerializer(m.RSForm(schema)).data
|
||||||
filename = utils.filename_for_schema(self._get_schema().alias)
|
file = utility.write_zipped_json(data, utils.EXTEOR_INNER_FILENAME)
|
||||||
|
filename = utils.filename_for_schema(schema.alias)
|
||||||
response = HttpResponse(file, content_type='application/zip')
|
response = HttpResponse(file, content_type='application/zip')
|
||||||
response['Content-Disposition'] = f'attachment; filename={filename}'
|
response['Content-Disposition'] = f'attachment; filename={filename}'
|
||||||
return response
|
return response
|
||||||
|
@ -440,33 +447,32 @@ class TrsImportView(views.APIView):
|
||||||
tags=['RSForm'],
|
tags=['RSForm'],
|
||||||
request=s.FileSerializer,
|
request=s.FileSerializer,
|
||||||
responses={
|
responses={
|
||||||
c.HTTP_201_CREATED: s.LibraryItemSerializer,
|
c.HTTP_201_CREATED: LibraryItemSerializer,
|
||||||
c.HTTP_403_FORBIDDEN: None
|
c.HTTP_403_FORBIDDEN: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request: Request):
|
def post(self, request: Request):
|
||||||
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
||||||
owner = cast(m.User, self.request.user)
|
owner = cast(User, self.request.user)
|
||||||
_prepare_rsform_data(data, request, owner)
|
_prepare_rsform_data(data, request, owner)
|
||||||
serializer = s.RSFormTRSSerializer(
|
serializer = s.RSFormTRSSerializer(
|
||||||
data=data,
|
data=data,
|
||||||
context={'load_meta': True}
|
context={'load_meta': True}
|
||||||
)
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema = serializer.save()
|
schema: m.RSForm = serializer.save()
|
||||||
result = s.LibraryItemSerializer(schema)
|
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
data=result.data
|
data=LibraryItemSerializer(schema.model).data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='create new RSForm empty or from file',
|
summary='create new RSForm empty or from file',
|
||||||
tags=['RSForm'],
|
tags=['RSForm'],
|
||||||
request=s.LibraryItemSerializer,
|
request=LibraryItemSerializer,
|
||||||
responses={
|
responses={
|
||||||
c.HTTP_201_CREATED: s.LibraryItemSerializer,
|
c.HTTP_201_CREATED: LibraryItemSerializer,
|
||||||
c.HTTP_400_BAD_REQUEST: None,
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
c.HTTP_403_FORBIDDEN: None
|
c.HTTP_403_FORBIDDEN: None
|
||||||
}
|
}
|
||||||
|
@ -474,26 +480,25 @@ class TrsImportView(views.APIView):
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def create_rsform(request: Request):
|
def create_rsform(request: Request):
|
||||||
''' Endpoint: Create RSForm from user input and/or trs file. '''
|
''' Endpoint: Create RSForm from user input and/or trs file. '''
|
||||||
owner = cast(m.User, request.user) if not request.user.is_anonymous else None
|
owner = cast(User, request.user) if not request.user.is_anonymous else None
|
||||||
if 'file' not in request.FILES:
|
if 'file' not in request.FILES:
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_400_BAD_REQUEST,
|
status=c.HTTP_400_BAD_REQUEST,
|
||||||
data={'file': msg.missingFile()}
|
data={'file': msg.missingFile()}
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
||||||
_prepare_rsform_data(data, request, owner)
|
_prepare_rsform_data(data, request, owner)
|
||||||
serializer_rsform = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
serializer_rsform = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
||||||
serializer_rsform.is_valid(raise_exception=True)
|
serializer_rsform.is_valid(raise_exception=True)
|
||||||
schema = serializer_rsform.save()
|
schema: m.RSForm = serializer_rsform.save()
|
||||||
result = s.LibraryItemSerializer(schema)
|
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
data=result.data
|
data=LibraryItemSerializer(schema.model).data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _prepare_rsform_data(data: dict, request: Request, owner: Union[m.User, None]):
|
def _prepare_rsform_data(data: dict, request: Request, owner: Union[User, None]):
|
||||||
data['owner'] = owner
|
data['owner'] = owner
|
||||||
if 'title' in request.data and request.data['title'] != '':
|
if 'title' in request.data and request.data['title'] != '':
|
||||||
data['title'] = request.data['title']
|
data['title'] = request.data['title']
|
||||||
|
@ -514,5 +519,5 @@ def _prepare_rsform_data(data: dict, request: Request, owner: Union[m.User, None
|
||||||
read_only = request.data['read_only'] == 'true'
|
read_only = request.data['read_only'] == 'true'
|
||||||
data['read_only'] = read_only
|
data['read_only'] = read_only
|
||||||
|
|
||||||
data['access_policy'] = request.data.get('access_policy', m.AccessPolicy.PUBLIC)
|
data['access_policy'] = request.data.get('access_policy', AccessPolicy.PUBLIC)
|
||||||
data['location'] = request.data.get('location', m.LocationHead.USER)
|
data['location'] = request.data.get('location', LocationHead.USER)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth.password_validation import validate_password
|
from django.contrib.auth.password_validation import validate_password
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from apps.rsform.models import Editor, Subscription
|
from apps.library.models import Editor, Subscription
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
|
@ -4668,7 +4668,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.editor",
|
"model": "library.editor",
|
||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"item": 35,
|
"item": 35,
|
||||||
|
@ -4677,7 +4677,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.subscription",
|
"model": "library.subscription",
|
||||||
"pk": 11,
|
"pk": 11,
|
||||||
"fields": {
|
"fields": {
|
||||||
"user": 1,
|
"user": 1,
|
||||||
|
@ -4685,7 +4685,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.subscription",
|
"model": "library.subscription",
|
||||||
"pk": 12,
|
"pk": 12,
|
||||||
"fields": {
|
"fields": {
|
||||||
"user": 5,
|
"user": 5,
|
||||||
|
@ -4693,7 +4693,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.subscription",
|
"model": "library.subscription",
|
||||||
"pk": 13,
|
"pk": 13,
|
||||||
"fields": {
|
"fields": {
|
||||||
"user": 3,
|
"user": 3,
|
||||||
|
@ -4701,7 +4701,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.subscription",
|
"model": "library.subscription",
|
||||||
"pk": 14,
|
"pk": 14,
|
||||||
"fields": {
|
"fields": {
|
||||||
"user": 3,
|
"user": 3,
|
||||||
|
@ -4709,7 +4709,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.libraryitem",
|
"model": "library.libraryitem",
|
||||||
"pk": 34,
|
"pk": 34,
|
||||||
"fields": {
|
"fields": {
|
||||||
"item_type": "rsform",
|
"item_type": "rsform",
|
||||||
|
@ -4726,7 +4726,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.libraryitem",
|
"model": "library.libraryitem",
|
||||||
"pk": 35,
|
"pk": 35,
|
||||||
"fields": {
|
"fields": {
|
||||||
"item_type": "rsform",
|
"item_type": "rsform",
|
||||||
|
@ -4743,7 +4743,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.libraryitem",
|
"model": "library.libraryitem",
|
||||||
"pk": 36,
|
"pk": 36,
|
||||||
"fields": {
|
"fields": {
|
||||||
"item_type": "rsform",
|
"item_type": "rsform",
|
||||||
|
@ -4760,7 +4760,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.libraryitem",
|
"model": "library.libraryitem",
|
||||||
"pk": 37,
|
"pk": 37,
|
||||||
"fields": {
|
"fields": {
|
||||||
"item_type": "rsform",
|
"item_type": "rsform",
|
||||||
|
@ -4777,7 +4777,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "rsform.librarytemplate",
|
"model": "library.librarytemplate",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"lib_source": 34
|
"lib_source": 34
|
||||||
|
|
|
@ -73,6 +73,7 @@ INSTALLED_APPS = [
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
|
|
||||||
'apps.users',
|
'apps.users',
|
||||||
|
'apps.library',
|
||||||
'apps.rsform',
|
'apps.rsform',
|
||||||
'apps.oss',
|
'apps.oss',
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, Spec
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin', admin.site.urls),
|
path('admin', admin.site.urls),
|
||||||
|
path('api/', include('apps.library.urls')),
|
||||||
path('api/', include('apps.rsform.urls')),
|
path('api/', include('apps.rsform.urls')),
|
||||||
path('api/', include('apps.oss.urls')),
|
path('api/', include('apps.oss.urls')),
|
||||||
path('users/', include('apps.users.urls')),
|
path('users/', include('apps.users.urls')),
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APIClient, APIRequestFactory, APITestCase
|
from rest_framework.test import APIClient, APIRequestFactory, APITestCase
|
||||||
|
|
||||||
from apps.rsform.models import Editor, LibraryItem
|
from apps.library.models import Editor, LibraryItem
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,15 +11,9 @@ from rest_framework.permissions import \
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from apps.library.models import AccessPolicy, Editor, LibraryItem, Subscription, Version
|
||||||
from apps.oss.models import Operation
|
from apps.oss.models import Operation
|
||||||
from apps.rsform.models import (
|
from apps.rsform.models import Constituenta
|
||||||
AccessPolicy,
|
|
||||||
Constituenta,
|
|
||||||
Editor,
|
|
||||||
LibraryItem,
|
|
||||||
Subscription,
|
|
||||||
Version
|
|
||||||
)
|
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
''' Utilities for testing. '''
|
''' Utilities for testing. '''
|
||||||
|
|
||||||
from apps.rsform.models import LibraryItem
|
from apps.library.models import LibraryItem
|
||||||
|
|
||||||
|
|
||||||
def response_contains(response, item: LibraryItem) -> bool:
|
def response_contains(response, item: LibraryItem) -> bool:
|
||||||
|
|
21
rsconcept/backend/shared/utility.py
Normal file
21
rsconcept/backend/shared/utility.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
''' Utility functions. '''
|
||||||
|
import json
|
||||||
|
from io import BytesIO
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
|
||||||
|
def read_zipped_json(data, json_filename: str) -> dict:
|
||||||
|
''' Read JSON from zipped data '''
|
||||||
|
with ZipFile(data, 'r') as archive:
|
||||||
|
json_data = archive.read(json_filename)
|
||||||
|
result: dict = json.loads(json_data)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def write_zipped_json(json_data: dict, json_filename: str) -> bytes:
|
||||||
|
''' Write json JSON to bytes buffer '''
|
||||||
|
content = BytesIO()
|
||||||
|
data = json.dumps(json_data, indent=4, ensure_ascii=False)
|
||||||
|
with ZipFile(content, 'w') as archive:
|
||||||
|
archive.writestr(json_filename, data=data)
|
||||||
|
return content.getvalue()
|
|
@ -7,9 +7,10 @@ import {
|
||||||
ILibraryItem,
|
ILibraryItem,
|
||||||
ILibraryUpdateData,
|
ILibraryUpdateData,
|
||||||
ITargetAccessPolicy,
|
ITargetAccessPolicy,
|
||||||
ITargetLocation
|
ITargetLocation,
|
||||||
|
IVersionData
|
||||||
} from '@/models/library';
|
} from '@/models/library';
|
||||||
import { IRSFormCloneData, IRSFormData } from '@/models/rsform';
|
import { IRSFormCloneData, IRSFormData, IVersionCreatedResponse } from '@/models/rsform';
|
||||||
import { ITargetUser, ITargetUsers } from '@/models/user';
|
import { ITargetUser, ITargetUsers } from '@/models/user';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -113,3 +114,10 @@ export function deleteUnsubscribe(target: string, request: FrontAction) {
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postCreateVersion(target: string, request: FrontExchange<IVersionData, IVersionCreatedResponse>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/library/${target}/create-version`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Endpoints: rsforms.
|
* Endpoints: rsforms.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ILibraryCreateData, ILibraryItem, IVersionData } from '@/models/library';
|
import { ILibraryCreateData, ILibraryItem } from '@/models/library';
|
||||||
import { ICstSubstituteData } from '@/models/oss';
|
import { ICstSubstituteData } from '@/models/oss';
|
||||||
import {
|
import {
|
||||||
IConstituentaList,
|
IConstituentaList,
|
||||||
|
@ -13,8 +13,7 @@ import {
|
||||||
IProduceStructureResponse,
|
IProduceStructureResponse,
|
||||||
IRSFormData,
|
IRSFormData,
|
||||||
IRSFormUploadData,
|
IRSFormUploadData,
|
||||||
ITargetCst,
|
ITargetCst
|
||||||
IVersionCreatedResponse
|
|
||||||
} from '@/models/rsform';
|
} from '@/models/rsform';
|
||||||
import { IExpressionParse, IRSExpression } from '@/models/rslang';
|
import { IExpressionParse, IRSExpression } from '@/models/rslang';
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ export function getRSFormDetails(target: string, version: string, request: Front
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
AxiosGet({
|
AxiosGet({
|
||||||
endpoint: `/api/rsforms/${target}/versions/${version}`,
|
endpoint: `/api/library/${target}/versions/${version}`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -136,10 +135,3 @@ export function patchUploadTRS(target: string, request: FrontExchange<IRSFormUpl
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postCreateVersion(target: string, request: FrontExchange<IVersionData, IVersionCreatedResponse>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/rsforms/${target}/versions/create`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
patchSetEditors,
|
patchSetEditors,
|
||||||
patchSetLocation,
|
patchSetLocation,
|
||||||
patchSetOwner,
|
patchSetOwner,
|
||||||
|
postCreateVersion,
|
||||||
postSubscribe
|
postSubscribe
|
||||||
} from '@/backend/library';
|
} from '@/backend/library';
|
||||||
import { patchInlineSynthesis } from '@/backend/operations';
|
import { patchInlineSynthesis } from '@/backend/operations';
|
||||||
|
@ -24,8 +25,7 @@ import {
|
||||||
patchRestoreOrder,
|
patchRestoreOrder,
|
||||||
patchSubstituteConstituents,
|
patchSubstituteConstituents,
|
||||||
patchUploadTRS,
|
patchUploadTRS,
|
||||||
postCreateConstituenta,
|
postCreateConstituenta
|
||||||
postCreateVersion
|
|
||||||
} from '@/backend/rsforms';
|
} from '@/backend/rsforms';
|
||||||
import { deleteVersion, patchRestoreVersion, patchVersion } from '@/backend/versions';
|
import { deleteVersion, patchRestoreVersion, patchVersion } from '@/backend/versions';
|
||||||
import { type ErrorData } from '@/components/info/InfoError';
|
import { type ErrorData } from '@/components/info/InfoError';
|
||||||
|
|
Loading…
Reference in New Issue
Block a user