Compare commits
10 Commits
338ad2bb98
...
2eff1b27b9
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2eff1b27b9 | ||
![]() |
3c961c8192 | ||
![]() |
583083c1fd | ||
![]() |
8632848207 | ||
![]() |
a611486c69 | ||
![]() |
ecb26c3908 | ||
![]() |
90dcf7a8eb | ||
![]() |
95caab2919 | ||
![]() |
c438b6ac16 | ||
![]() |
de8e0e60ac |
11
README.md
11
README.md
|
@ -11,13 +11,14 @@
|
||||||
[](https://github.com/IRBorisov/ConceptPortal/actions/workflows/frontend.yml)
|
[](https://github.com/IRBorisov/ConceptPortal/actions/workflows/frontend.yml)
|
||||||
|
|
||||||
React + Django based web portal for editing RSForm schemas.
|
React + Django based web portal for editing RSForm schemas.
|
||||||
This readme file is used mostly to document project dependencies
|
This readme file is used mostly to document project dependencies and conventions.
|
||||||
|
|
||||||
## ❤️ Contributing notes
|
## ❤️ Contributing notes
|
||||||
|
|
||||||
- feel free to open issues, discussion topics, contact maintainer directly
|
- feel free to open issues, discussion topics, contact maintainer directly
|
||||||
- use Test config in VSCode to run tests before pushing commits / requests
|
- use Test config in VSCode to run tests before pushing commits / requests
|
||||||
- use github actions to setup linter checks and test builds
|
- use github actions to setup linter checks and test builds
|
||||||
|
- use conventional commits to describe changes
|
||||||
|
|
||||||
## ✨ Frontend [Vite + React + Typescript]
|
## ✨ Frontend [Vite + React + Typescript]
|
||||||
|
|
||||||
|
@ -138,6 +139,14 @@ This readme file is used mostly to document project dependencies
|
||||||
|
|
||||||
# Developer Notes
|
# Developer Notes
|
||||||
|
|
||||||
|
## 📝 Commit conventions
|
||||||
|
|
||||||
|
- 🚀 F: major feature implementation
|
||||||
|
- 💄 D: UI design
|
||||||
|
- 🚑 B: bug fix
|
||||||
|
- 🔧 R: refactoring and code improvement
|
||||||
|
- 📝 I: documentation
|
||||||
|
|
||||||
## 🖥️ Local build (Windows 10+)
|
## 🖥️ Local build (Windows 10+)
|
||||||
|
|
||||||
This is the build for local Development
|
This is the build for local Development
|
||||||
|
|
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='Владелец',
|
||||||
|
@ -128,7 +129,30 @@ class LibraryItem(Model):
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
subscribe = not self.pk and self.owner
|
''' Save updating subscriptions and connected operations. '''
|
||||||
|
if not self._state.adding:
|
||||||
|
self._update_connected_operations()
|
||||||
|
subscribe = self._state.adding and self.owner
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
if subscribe:
|
if subscribe:
|
||||||
Subscription.subscribe(user=self.owner, item=self)
|
Subscription.subscribe(user=self.owner, item=self)
|
||||||
|
|
||||||
|
def _update_connected_operations(self):
|
||||||
|
# using method level import to prevent circular dependency
|
||||||
|
from apps.oss.models import Operation # pylint: disable=import-outside-toplevel
|
||||||
|
operations = Operation.objects.filter(result__pk=self.pk, sync_text=True)
|
||||||
|
if not operations.exists():
|
||||||
|
return
|
||||||
|
for operation in operations:
|
||||||
|
changed = False
|
||||||
|
if operation.alias != self.alias:
|
||||||
|
operation.alias = self.alias
|
||||||
|
changed = True
|
||||||
|
if operation.title != self.title:
|
||||||
|
operation.title = self.title
|
||||||
|
changed = True
|
||||||
|
if operation.comment != self.comment:
|
||||||
|
operation.comment = self.comment
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
operation.save()
|
|
@ -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
|
||||||
|
@ -179,7 +183,7 @@ class TestLibraryViewset(EndpointTester):
|
||||||
self.unowned.refresh_from_db()
|
self.unowned.refresh_from_db()
|
||||||
self.assertEqual(self.unowned.location, data['location'])
|
self.assertEqual(self.unowned.location, data['location'])
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/editors-add', method='patch')
|
@decl_endpoint('/api/library/{item}/add-editor', method='patch')
|
||||||
def test_add_editor(self):
|
def test_add_editor(self):
|
||||||
time_update = self.owned.time_update
|
time_update = self.owned.time_update
|
||||||
|
|
||||||
|
@ -203,7 +207,7 @@ class TestLibraryViewset(EndpointTester):
|
||||||
self.assertEqual(set(self.owned.editors()), set([self.user, self.user2]))
|
self.assertEqual(set(self.owned.editors()), set([self.user, self.user2]))
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/editors-remove', method='patch')
|
@decl_endpoint('/api/library/{item}/remove-editor', method='patch')
|
||||||
def test_remove_editor(self):
|
def test_remove_editor(self):
|
||||||
time_update = self.owned.time_update
|
time_update = self.owned.time_update
|
||||||
|
|
||||||
|
@ -230,7 +234,7 @@ class TestLibraryViewset(EndpointTester):
|
||||||
self.assertEqual(self.owned.editors(), [self.user])
|
self.assertEqual(self.owned.editors(), [self.user])
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/editors-set', method='patch')
|
@decl_endpoint('/api/library/{item}/set-editors', method='patch')
|
||||||
def test_set_editors(self):
|
def test_set_editors(self):
|
||||||
time_update = self.owned.time_update
|
time_update = self.owned.time_update
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -49,9 +52,9 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
'set_owner',
|
'set_owner',
|
||||||
'set_access_policy',
|
'set_access_policy',
|
||||||
'set_location',
|
'set_location',
|
||||||
'editors_add',
|
'add_editor',
|
||||||
'editors_remove',
|
'remove_editor',
|
||||||
'editors_set'
|
'set_editors'
|
||||||
]:
|
]:
|
||||||
access_level = permissions.ItemOwner
|
access_level = permissions.ItemOwner
|
||||||
elif self.action in [
|
elif self.action in [
|
||||||
|
@ -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(
|
||||||
|
@ -220,8 +223,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='editors-add')
|
@action(detail=True, methods=['patch'], url_path='add-editor')
|
||||||
def editors_add(self, request: Request, pk):
|
def add_editor(self, request: Request, pk):
|
||||||
''' Endpoint: Add editor for item. '''
|
''' Endpoint: Add editor for item. '''
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
serializer = s.UserTargetSerializer(data=request.data)
|
serializer = s.UserTargetSerializer(data=request.data)
|
||||||
|
@ -240,8 +243,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='editors-remove')
|
@action(detail=True, methods=['patch'], url_path='remove-editor')
|
||||||
def editors_remove(self, request: Request, pk):
|
def remove_editor(self, request: Request, pk):
|
||||||
''' Endpoint: Remove editor for item. '''
|
''' Endpoint: Remove editor for item. '''
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
serializer = s.UserTargetSerializer(data=request.data)
|
serializer = s.UserTargetSerializer(data=request.data)
|
||||||
|
@ -260,8 +263,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='editors-set')
|
@action(detail=True, methods=['patch'], url_path='set-editors')
|
||||||
def editors_set(self, request: Request, pk):
|
def set_editors(self, request: Request, pk):
|
||||||
''' Endpoint: Set list of editors for item. '''
|
''' Endpoint: Set list of editors for item. '''
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
serializer = s.UsersListSerializer(data=request.data)
|
serializer = s.UsersListSerializer(data=request.data)
|
||||||
|
@ -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,84 +44,13 @@ 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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='export versioned data as file',
|
summary='export versioned data as file',
|
||||||
tags=['Version'],
|
tags=['Version'],
|
||||||
|
@ -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': 'Аргумент',
|
||||||
|
|
28
rsconcept/backend/apps/oss/migrations/0002_inheritance.py
Normal file
28
rsconcept/backend/apps/oss/migrations/0002_inheritance.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 5.0.7 on 2024-07-25 18:08
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('oss', '0001_initial'),
|
||||||
|
('rsform', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Inheritance',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='as_child', to='rsform.constituenta', verbose_name='Наследованная конституента')),
|
||||||
|
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='as_parent', to='rsform.constituenta', verbose_name='Исходная конституента')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Наследование синтеза',
|
||||||
|
'verbose_name_plural': 'Отношение наследования конституент',
|
||||||
|
'unique_together': {('parent', 'child')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -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='Схема синтеза'),
|
|
||||||
),
|
|
||||||
]
|
|
28
rsconcept/backend/apps/oss/models/Inheritance.py
Normal file
28
rsconcept/backend/apps/oss/models/Inheritance.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
''' Models: Synthesis Inheritance. '''
|
||||||
|
from django.db.models import CASCADE, ForeignKey, Model
|
||||||
|
|
||||||
|
|
||||||
|
class Inheritance(Model):
|
||||||
|
''' Inheritance links parent and child constituents in synthesis operation.'''
|
||||||
|
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 = 'Отношение наследования конституент'
|
||||||
|
unique_together = [['parent', 'child']]
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'{self.parent} -> {self.child}'
|
|
@ -2,6 +2,7 @@
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
CASCADE,
|
CASCADE,
|
||||||
SET_NULL,
|
SET_NULL,
|
||||||
|
BooleanField,
|
||||||
CharField,
|
CharField,
|
||||||
FloatField,
|
FloatField,
|
||||||
ForeignKey,
|
ForeignKey,
|
||||||
|
@ -21,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'
|
||||||
)
|
)
|
||||||
|
@ -33,11 +34,15 @@ class Operation(Model):
|
||||||
)
|
)
|
||||||
result: ForeignKey = ForeignKey(
|
result: ForeignKey = ForeignKey(
|
||||||
verbose_name='Связанная КС',
|
verbose_name='Связанная КС',
|
||||||
to='rsform.LibraryItem',
|
to='library.LibraryItem',
|
||||||
null=True,
|
null=True,
|
||||||
on_delete=SET_NULL,
|
on_delete=SET_NULL,
|
||||||
related_name='producer'
|
related_name='producer'
|
||||||
)
|
)
|
||||||
|
sync_text: BooleanField = BooleanField(
|
||||||
|
verbose_name='Синхронизация',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
alias: CharField = CharField(
|
alias: CharField = CharField(
|
||||||
verbose_name='Шифр',
|
verbose_name='Шифр',
|
||||||
|
|
|
@ -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'],
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
''' Models: SynthesisSubstitution. '''
|
''' Models: Synthesis Substitution. '''
|
||||||
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,7 @@
|
||||||
''' Django: Models. '''
|
''' Django: Models. '''
|
||||||
|
|
||||||
from apps.rsform.models import LibraryItem, LibraryItemType
|
|
||||||
|
|
||||||
from .Argument import Argument
|
from .Argument import Argument
|
||||||
|
from .Inheritance import Inheritance
|
||||||
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
|
||||||
|
@ -41,9 +41,10 @@ class OperationCreateSerializer(serializers.Serializer):
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
model = Operation
|
model = Operation
|
||||||
fields = \
|
fields = \
|
||||||
'alias', 'operation_type', 'title', \
|
'alias', 'operation_type', 'title', 'sync_text', \
|
||||||
'comment', 'result', 'position_x', 'position_y'
|
'comment', 'result', 'position_x', 'position_y'
|
||||||
|
|
||||||
|
create_schema = serializers.BooleanField(default=False, required=False)
|
||||||
item_data = OperationData()
|
item_data = OperationData()
|
||||||
arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
|
arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
|
||||||
positions = serializers.ListField(
|
positions = serializers.ListField(
|
||||||
|
@ -85,19 +86,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,16 +1,18 @@
|
||||||
''' Testing models: Operation. '''
|
''' Testing models: Operation. '''
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from apps.library.models import LibraryItem, LibraryItemType
|
||||||
from apps.oss.models import Operation, OperationSchema, OperationType
|
from apps.oss.models import Operation, OperationSchema, OperationType
|
||||||
|
from apps.rsform.models import RSForm
|
||||||
|
|
||||||
|
|
||||||
class TestOperation(TestCase):
|
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'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,11 +23,54 @@ 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')
|
||||||
self.assertEqual(self.operation.title, '')
|
self.assertEqual(self.operation.title, '')
|
||||||
self.assertEqual(self.operation.comment, '')
|
self.assertEqual(self.operation.comment, '')
|
||||||
|
self.assertEqual(self.operation.sync_text, True)
|
||||||
self.assertEqual(self.operation.position_x, 0)
|
self.assertEqual(self.operation.position_x, 0)
|
||||||
self.assertEqual(self.operation.position_y, 0)
|
self.assertEqual(self.operation.position_y, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_from_result(self):
|
||||||
|
schema = RSForm.create(alias=self.operation.alias)
|
||||||
|
self.operation.result = schema.model
|
||||||
|
self.operation.save()
|
||||||
|
|
||||||
|
schema.model.alias = 'KS2'
|
||||||
|
schema.model.comment = 'Comment'
|
||||||
|
schema.model.title = 'Title'
|
||||||
|
schema.save()
|
||||||
|
self.operation.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(self.operation.result, schema.model)
|
||||||
|
self.assertEqual(self.operation.alias, schema.model.alias)
|
||||||
|
self.assertEqual(self.operation.title, schema.model.title)
|
||||||
|
self.assertEqual(self.operation.comment, schema.model.comment)
|
||||||
|
|
||||||
|
self.operation.sync_text = False
|
||||||
|
self.operation.save()
|
||||||
|
|
||||||
|
schema.model.alias = 'KS3'
|
||||||
|
schema.save()
|
||||||
|
self.operation.refresh_from_db()
|
||||||
|
self.assertEqual(self.operation.result, schema.model)
|
||||||
|
self.assertNotEqual(self.operation.alias, schema.model.alias)
|
||||||
|
|
||||||
|
def test_sync_from_library_item(self):
|
||||||
|
schema = LibraryItem.objects.create(alias=self.operation.alias, item_type=LibraryItemType.RSFORM)
|
||||||
|
self.operation.result = schema
|
||||||
|
self.operation.save()
|
||||||
|
|
||||||
|
schema.alias = 'KS2'
|
||||||
|
schema.comment = 'Comment'
|
||||||
|
schema.title = 'Title'
|
||||||
|
schema.save()
|
||||||
|
self.operation.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(self.operation.result, schema)
|
||||||
|
self.assertEqual(self.operation.alias, schema.alias)
|
||||||
|
self.assertEqual(self.operation.title, schema.title)
|
||||||
|
self.assertEqual(self.operation.comment, schema.comment)
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
''' Testing models: SynthesisSubstitution. '''
|
''' Testing models: Synthesis Substitution. '''
|
||||||
from unittest import result
|
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
|
||||||
|
|
||||||
|
|
||||||
class TestSynthesisSubstitution(TestCase):
|
class TestSynthesisSubstitution(TestCase):
|
||||||
''' Testing SynthesisSubstitution 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)
|
||||||
|
@ -136,6 +137,7 @@ class TestOssViewset(EndpointTester):
|
||||||
'alias': 'Test3',
|
'alias': 'Test3',
|
||||||
'title': 'Test title',
|
'title': 'Test title',
|
||||||
'comment': 'Тест кириллицы',
|
'comment': 'Тест кириллицы',
|
||||||
|
'sync_text': False,
|
||||||
'position_x': 1,
|
'position_x': 1,
|
||||||
'position_y': 1,
|
'position_y': 1,
|
||||||
},
|
},
|
||||||
|
@ -149,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'])
|
||||||
|
@ -158,6 +162,7 @@ class TestOssViewset(EndpointTester):
|
||||||
self.assertEqual(new_operation['comment'], data['item_data']['comment'])
|
self.assertEqual(new_operation['comment'], data['item_data']['comment'])
|
||||||
self.assertEqual(new_operation['position_x'], data['item_data']['position_x'])
|
self.assertEqual(new_operation['position_x'], data['item_data']['position_x'])
|
||||||
self.assertEqual(new_operation['position_y'], data['item_data']['position_y'])
|
self.assertEqual(new_operation['position_y'], data['item_data']['position_y'])
|
||||||
|
self.assertEqual(new_operation['sync_text'], data['item_data']['sync_text'])
|
||||||
self.assertEqual(new_operation['result'], None)
|
self.assertEqual(new_operation['result'], None)
|
||||||
self.operation1.refresh_from_db()
|
self.operation1.refresh_from_db()
|
||||||
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
|
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
|
||||||
|
@ -193,15 +198,55 @@ 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}/create-operation', method='post')
|
||||||
|
def test_create_operation_schema(self):
|
||||||
|
self.populateData()
|
||||||
|
data = {
|
||||||
|
'item_data': {
|
||||||
|
'alias': 'Test4',
|
||||||
|
'title': 'Test title',
|
||||||
|
'comment': 'Comment',
|
||||||
|
'operation_type': OperationType.INPUT
|
||||||
|
},
|
||||||
|
'create_schema': True,
|
||||||
|
'positions': [],
|
||||||
|
}
|
||||||
|
response = self.executeCreated(data=data, item=self.owned_id)
|
||||||
|
self.owned.refresh_from_db()
|
||||||
|
new_operation = response.data['new_operation']
|
||||||
|
schema = LibraryItem.objects.get(pk=new_operation['result'])
|
||||||
|
self.assertEqual(schema.alias, data['item_data']['alias'])
|
||||||
|
self.assertEqual(schema.title, data['item_data']['title'])
|
||||||
|
self.assertEqual(schema.comment, data['item_data']['comment'])
|
||||||
|
self.assertEqual(schema.visible, False)
|
||||||
|
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
||||||
|
self.assertEqual(schema.location, self.owned.model.location)
|
||||||
|
|
||||||
|
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||||
|
def test_create_operation_result(self):
|
||||||
|
self.populateData()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'item_data': {
|
||||||
|
'alias': 'Test4',
|
||||||
|
'operation_type': OperationType.INPUT,
|
||||||
|
'result': self.ks1.model.pk
|
||||||
|
},
|
||||||
|
'positions': [],
|
||||||
|
}
|
||||||
|
response = self.executeCreated(data=data, item=self.owned_id)
|
||||||
|
self.owned.refresh_from_db()
|
||||||
|
new_operation = response.data['new_operation']
|
||||||
|
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')
|
||||||
def test_delete_operation(self):
|
def test_delete_operation(self):
|
||||||
|
|
|
@ -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,36 @@ 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'])
|
data: dict = serializer.validated_data['item_data']
|
||||||
|
if data['operation_type'] == m.OperationType.INPUT and serializer.validated_data['create_schema']:
|
||||||
|
schema = LibraryItem.objects.create(
|
||||||
|
item_type=LibraryItemType.RSFORM,
|
||||||
|
owner=oss.model.owner,
|
||||||
|
alias=data['alias'],
|
||||||
|
title=data['title'],
|
||||||
|
comment=data['comment'],
|
||||||
|
visible=False,
|
||||||
|
access_policy=oss.model.access_policy,
|
||||||
|
location=oss.model.location
|
||||||
|
)
|
||||||
|
data['result'] = schema
|
||||||
|
new_operation = oss.create_operation(**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 +140,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:
|
||||||
|
@ -186,7 +179,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.assertIn('document.json', zipped_file.namelist())
|
self.assertIn('document.json', zipped_file.namelist())
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/cst-create', method='post')
|
@decl_endpoint('/api/rsforms/{item}/create-cst', method='post')
|
||||||
def test_create_constituenta(self):
|
def test_create_constituenta(self):
|
||||||
data = {'alias': 'X3'}
|
data = {'alias': 'X3'}
|
||||||
self.executeForbidden(data=data, item=self.unowned_id)
|
self.executeForbidden(data=data, item=self.unowned_id)
|
||||||
|
@ -229,7 +222,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
|
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/cst-rename', method='patch')
|
@decl_endpoint('/api/rsforms/{item}/rename-cst', method='patch')
|
||||||
def test_rename_constituenta(self):
|
def test_rename_constituenta(self):
|
||||||
x1 = self.owned.insert_new(
|
x1 = self.owned.insert_new(
|
||||||
alias='X1',
|
alias='X1',
|
||||||
|
@ -279,7 +272,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.assertEqual(x1.cst_type, CstType.TERM)
|
self.assertEqual(x1.cst_type, CstType.TERM)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch')
|
@decl_endpoint('/api/rsforms/{item}/substitute', method='patch')
|
||||||
def test_substitute_single(self):
|
def test_substitute_single(self):
|
||||||
x1 = self.owned.insert_new(
|
x1 = self.owned.insert_new(
|
||||||
alias='X1',
|
alias='X1',
|
||||||
|
@ -316,7 +309,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.assertEqual(d1.term_resolved, 'form1')
|
self.assertEqual(d1.term_resolved, 'form1')
|
||||||
self.assertEqual(d1.definition_formal, 'X2')
|
self.assertEqual(d1.definition_formal, 'X2')
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch')
|
@decl_endpoint('/api/rsforms/{item}/substitute', method='patch')
|
||||||
def test_substitute_multiple(self):
|
def test_substitute_multiple(self):
|
||||||
self.set_params(item=self.owned_id)
|
self.set_params(item=self.owned_id)
|
||||||
x1 = self.owned.insert_new('X1')
|
x1 = self.owned.insert_new('X1')
|
||||||
|
@ -362,7 +355,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.assertEqual(d3.definition_formal, r'D1 \ D2')
|
self.assertEqual(d3.definition_formal, r'D1 \ D2')
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/cst-create', method='post')
|
@decl_endpoint('/api/rsforms/{item}/create-cst', method='post')
|
||||||
def test_create_constituenta_data(self):
|
def test_create_constituenta_data(self):
|
||||||
data = {
|
data = {
|
||||||
'alias': 'X3',
|
'alias': 'X3',
|
||||||
|
@ -383,7 +376,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.assertEqual(response.data['new_cst']['definition_resolved'], '4')
|
self.assertEqual(response.data['new_cst']['definition_resolved'], '4')
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/cst-delete-multiple', method='patch')
|
@decl_endpoint('/api/rsforms/{item}/delete-multiple-cst', method='patch')
|
||||||
def test_delete_constituenta(self):
|
def test_delete_constituenta(self):
|
||||||
self.set_params(item=self.owned_id)
|
self.set_params(item=self.owned_id)
|
||||||
|
|
||||||
|
@ -407,7 +400,7 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.executeBadData(data=data, item=self.owned_id)
|
self.executeBadData(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/cst-moveto', method='patch')
|
@decl_endpoint('/api/rsforms/{item}/move-cst', method='patch')
|
||||||
def test_move_constituenta(self):
|
def test_move_constituenta(self):
|
||||||
self.set_params(item=self.owned_id)
|
self.set_params(item=self.owned_id)
|
||||||
|
|
||||||
|
@ -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,13 +460,13 @@ 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())
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/rsforms/{item}/cst-produce-structure', method='patch')
|
@decl_endpoint('/api/rsforms/{item}/produce-structure', method='patch')
|
||||||
def test_produce_structure(self):
|
def test_produce_structure(self):
|
||||||
self.set_params(item=self.owned_id)
|
self.set_params(item=self.owned_id)
|
||||||
x1 = self.owned.insert_new('X1')
|
x1 = self.owned.insert_new('X1')
|
||||||
|
|
|
@ -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,21 +28,24 @@ 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. '''
|
||||||
if self.action in [
|
if self.action in [
|
||||||
'load_trs',
|
'load_trs',
|
||||||
|
'create_cst',
|
||||||
|
'delete_multiple_cst',
|
||||||
|
'rename_cst',
|
||||||
|
'move_cst',
|
||||||
|
'substitute',
|
||||||
|
'restore_order',
|
||||||
'reset_aliases',
|
'reset_aliases',
|
||||||
'cst_create',
|
'produce_structure'
|
||||||
'cst_delete_multiple',
|
|
||||||
'cst_rename',
|
|
||||||
'cst_substitute'
|
|
||||||
]:
|
]:
|
||||||
permission_list = [permissions.ItemEditor]
|
permission_list = [permissions.ItemEditor]
|
||||||
elif self.action in [
|
elif self.action in [
|
||||||
|
@ -65,21 +71,21 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['post'], url_path='cst-create')
|
@action(detail=True, methods=['post'], url_path='create-cst')
|
||||||
def cst_create(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(
|
||||||
|
@ -103,10 +109,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-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)
|
||||||
|
@ -120,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={
|
||||||
|
@ -140,10 +146,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-rename')
|
@action(detail=True, methods=['patch'], url_path='rename-cst')
|
||||||
def cst_rename(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)
|
||||||
|
|
||||||
|
@ -155,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()
|
|
||||||
cst.refresh_from_db()
|
|
||||||
|
|
||||||
|
schema.refresh_from_db()
|
||||||
|
cst.refresh_from_db()
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data={
|
data={
|
||||||
|
@ -178,10 +184,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-substitute')
|
@action(detail=True, methods=['patch'], url_path='substitute')
|
||||||
def cst_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}
|
||||||
|
@ -192,8 +198,9 @@ 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,
|
||||||
data=s.RSFormParseSerializer(schema).data
|
data=s.RSFormParseSerializer(schema).data
|
||||||
|
@ -210,16 +217,17 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-delete-multiple')
|
@action(detail=True, methods=['patch'], url_path='delete-multiple-cst')
|
||||||
def cst_delete_multiple(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,
|
||||||
|
@ -237,16 +245,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
@action(detail=True, methods=['patch'], url_path='move-cst')
|
||||||
def cst_moveto(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']
|
||||||
)
|
)
|
||||||
|
@ -268,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
|
||||||
|
@ -288,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
|
||||||
|
@ -311,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(
|
||||||
|
@ -321,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(
|
||||||
|
@ -339,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(
|
||||||
|
@ -357,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
|
||||||
|
@ -378,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)
|
||||||
|
@ -400,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,
|
||||||
|
@ -419,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
|
||||||
|
@ -437,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
|
||||||
}
|
}
|
||||||
|
@ -471,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']
|
||||||
|
@ -511,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
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
426
rsconcept/frontend/package-lock.json
generated
426
rsconcept/frontend/package-lock.json
generated
|
@ -14,7 +14,7 @@
|
||||||
"@uiw/react-codemirror": "^4.23.0",
|
"@uiw/react-codemirror": "^4.23.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^11.3.8",
|
"framer-motion": "^11.3.17",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
@ -36,24 +36,24 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lezer/generator": "^1.7.1",
|
"@lezer/generator": "^1.7.1",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^20.14.11",
|
"@types/node": "^20.14.12",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||||
"@typescript-eslint/parser": "^7.16.1",
|
"@typescript-eslint/parser": "^7.17.0",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-react-refresh": "^0.4.8",
|
"eslint-plugin-react-refresh": "^0.4.9",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-tsdoc": "^0.3.0",
|
"eslint-plugin-tsdoc": "^0.3.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.40",
|
||||||
"tailwindcss": "^3.4.6",
|
"tailwindcss": "^3.4.7",
|
||||||
"ts-jest": "^29.2.3",
|
"ts-jest": "^29.2.3",
|
||||||
"typescript": "^5.5.3",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.3.4"
|
"vite": "^5.3.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
|
@ -740,9 +740,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.28.6",
|
"version": "6.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.29.0.tgz",
|
||||||
"integrity": "sha512-bhwB1AZ6zU4M3dNKm8Aa2BXwj5mWDqE9IWpqxYKJoLCnx+AcwcMuLO01tLWgc1mx4vT1IVYVqx86YoqUsATrqQ==",
|
"integrity": "sha512-ED4ims4fkf7eOA+HYLVP8VVg3NMllt1FPm9PEJBfYFnidKlRITBaua38u68L1F60eNtw2YNcDN5jsIzhKZwWQA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.4.0",
|
"@codemirror/state": "^6.4.0",
|
||||||
|
@ -797,14 +797,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/cache": {
|
"node_modules/@emotion/cache": {
|
||||||
"version": "11.12.0",
|
"version": "11.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz",
|
||||||
"integrity": "sha512-VFo/F1PthkxHwWDCcXkidyXw70eAkdiNiCzthMI2rRQjFiTvmXt8UDlv/VE1DTsd4CIEY2wQf5AnL2QiPgphlw==",
|
"integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/memoize": "^0.9.0",
|
"@emotion/memoize": "^0.9.0",
|
||||||
"@emotion/sheet": "^1.3.0",
|
"@emotion/sheet": "^1.4.0",
|
||||||
"@emotion/utils": "^1.3.0",
|
"@emotion/utils": "^1.4.0",
|
||||||
"@emotion/weak-memoize": "^0.4.0",
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
"stylis": "4.2.0"
|
"stylis": "4.2.0"
|
||||||
}
|
}
|
||||||
|
@ -837,17 +837,17 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/react": {
|
"node_modules/@emotion/react": {
|
||||||
"version": "11.12.0",
|
"version": "11.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz",
|
||||||
"integrity": "sha512-kTktYMpG8mHjLi8u6XOTMfDmQvUve/un2ZVj4khcU2KTn17ElMV8BK6QFzT8V/v2QW8013rf07Yc0ayQL3tp3w==",
|
"integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.3",
|
"@babel/runtime": "^7.18.3",
|
||||||
"@emotion/babel-plugin": "^11.12.0",
|
"@emotion/babel-plugin": "^11.12.0",
|
||||||
"@emotion/cache": "^11.12.0",
|
"@emotion/cache": "^11.13.0",
|
||||||
"@emotion/serialize": "^1.2.0",
|
"@emotion/serialize": "^1.3.0",
|
||||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
|
"@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
|
||||||
"@emotion/utils": "^1.3.0",
|
"@emotion/utils": "^1.4.0",
|
||||||
"@emotion/weak-memoize": "^0.4.0",
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
"hoist-non-react-statics": "^3.3.1"
|
"hoist-non-react-statics": "^3.3.1"
|
||||||
},
|
},
|
||||||
|
@ -861,22 +861,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/serialize": {
|
"node_modules/@emotion/serialize": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz",
|
||||||
"integrity": "sha512-X5UWpZAhGGp5LOn7OAI9k9JjRtz7nSFhZypatADcuEd/0bECZ0DzVjPdL8hljTrAku8+TjFvWIYHMOCO/0v/Ng==",
|
"integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/hash": "^0.9.2",
|
"@emotion/hash": "^0.9.2",
|
||||||
"@emotion/memoize": "^0.9.0",
|
"@emotion/memoize": "^0.9.0",
|
||||||
"@emotion/unitless": "^0.9.0",
|
"@emotion/unitless": "^0.9.0",
|
||||||
"@emotion/utils": "^1.3.0",
|
"@emotion/utils": "^1.4.0",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/sheet": {
|
"node_modules/@emotion/sheet": {
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||||
"integrity": "sha512-vOPwbKw8fj/oSEa7CWqiKCvLZ1AeLIAApmboGP34xUyUjXalFyf+tMtgMDqP7VMevLPhUa+YWJS46cQUA+tr9A==",
|
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/unitless": {
|
"node_modules/@emotion/unitless": {
|
||||||
|
@ -886,18 +886,18 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
|
||||||
"integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
|
"integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=16.8.0"
|
"react": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/utils": {
|
"node_modules/@emotion/utils": {
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz",
|
||||||
"integrity": "sha512-+M7u4EaX5t4bCunKTltAdGis3NFHQniikLVEQ+rPQccsX/xV4v5Etwg12paioZ9DsO+CTvimtmnjZbW85kbF8Q==",
|
"integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/weak-memoize": {
|
"node_modules/@emotion/weak-memoize": {
|
||||||
|
@ -1411,28 +1411,28 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/core": {
|
"node_modules/@floating-ui/core": {
|
||||||
"version": "1.6.4",
|
"version": "1.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz",
|
||||||
"integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==",
|
"integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/utils": "^0.2.4"
|
"@floating-ui/utils": "^0.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/dom": {
|
"node_modules/@floating-ui/dom": {
|
||||||
"version": "1.6.7",
|
"version": "1.6.8",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz",
|
||||||
"integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==",
|
"integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/core": "^1.6.0",
|
"@floating-ui/core": "^1.6.0",
|
||||||
"@floating-ui/utils": "^0.2.4"
|
"@floating-ui/utils": "^0.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/utils": {
|
"node_modules/@floating-ui/utils": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz",
|
||||||
"integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==",
|
"integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/ecma402-abstract": {
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
@ -2969,9 +2969,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz",
|
||||||
"integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==",
|
"integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
@ -2983,9 +2983,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz",
|
||||||
"integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==",
|
"integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -2997,9 +2997,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz",
|
||||||
"integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==",
|
"integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -3011,9 +3011,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz",
|
||||||
"integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==",
|
"integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -3025,9 +3025,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz",
|
||||||
"integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==",
|
"integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
@ -3039,9 +3039,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz",
|
||||||
"integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==",
|
"integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
@ -3053,9 +3053,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz",
|
||||||
"integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==",
|
"integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -3067,9 +3067,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz",
|
||||||
"integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==",
|
"integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -3081,9 +3081,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz",
|
||||||
"integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==",
|
"integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
@ -3095,9 +3095,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz",
|
||||||
"integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==",
|
"integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
@ -3109,9 +3109,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz",
|
||||||
"integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==",
|
"integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
@ -3123,9 +3123,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz",
|
||||||
"integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==",
|
"integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -3137,9 +3137,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz",
|
||||||
"integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==",
|
"integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -3151,9 +3151,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz",
|
||||||
"integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==",
|
"integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -3165,9 +3165,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz",
|
||||||
"integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==",
|
"integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
@ -3179,9 +3179,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz",
|
||||||
"integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==",
|
"integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -3253,9 +3253,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tweenjs/tween.js": {
|
"node_modules/@tweenjs/tween.js": {
|
||||||
"version": "23.1.2",
|
"version": "23.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||||
"integrity": "sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ==",
|
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
|
@ -3634,9 +3634,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.14.11",
|
"version": "20.14.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz",
|
||||||
"integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
|
"integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -3719,9 +3719,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/three": {
|
"node_modules/@types/three": {
|
||||||
"version": "0.166.0",
|
"version": "0.167.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.166.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.0.tgz",
|
||||||
"integrity": "sha512-FHMnpcdhdbdOOIYbfkTkUVpYMW53odxbTRwd0/xJpYnTzEsjnVnondGAvHZb4z06UW0vo6WPVuvH0/9qrxKx7g==",
|
"integrity": "sha512-BC+Vbm0d6yMzct7dhTBe9ZjEh6ygupyn1k/UcZncIIS/5aNIbfvF77gQw1IFP09Oyj1UxWj0EUBBqc1GkqzsOw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -3756,17 +3756,17 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "7.16.1",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz",
|
||||||
"integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==",
|
"integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "7.16.1",
|
"@typescript-eslint/scope-manager": "7.17.0",
|
||||||
"@typescript-eslint/type-utils": "7.16.1",
|
"@typescript-eslint/type-utils": "7.17.0",
|
||||||
"@typescript-eslint/utils": "7.16.1",
|
"@typescript-eslint/utils": "7.17.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.16.1",
|
"@typescript-eslint/visitor-keys": "7.17.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
|
@ -3790,16 +3790,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "7.16.1",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz",
|
||||||
"integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==",
|
"integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "7.16.1",
|
"@typescript-eslint/scope-manager": "7.17.0",
|
||||||
"@typescript-eslint/types": "7.16.1",
|
"@typescript-eslint/types": "7.17.0",
|
||||||
"@typescript-eslint/typescript-estree": "7.16.1",
|
"@typescript-eslint/typescript-estree": "7.17.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.16.1",
|
"@typescript-eslint/visitor-keys": "7.17.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -3819,14 +3819,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "7.16.1",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz",
|
||||||
"integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==",
|
"integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.16.1",
|
"@typescript-eslint/types": "7.17.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.16.1"
|
"@typescript-eslint/visitor-keys": "7.17.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || >=20.0.0"
|
"node": "^18.18.0 || >=20.0.0"
|
||||||
|
@ -3837,14 +3837,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "7.16.1",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz",
|
||||||
"integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==",
|
"integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "7.16.1",
|
"@typescript-eslint/typescript-estree": "7.17.0",
|
||||||
"@typescript-eslint/utils": "7.16.1",
|
"@typescript-eslint/utils": "7.17.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
|
@ -3865,9 +3865,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "7.16.1",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz",
|
||||||
"integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==",
|
"integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -3879,14 +3879,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "7.16.1",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz",
|
||||||
"integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==",
|
"integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.16.1",
|
"@typescript-eslint/types": "7.17.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.16.1",
|
"@typescript-eslint/visitor-keys": "7.17.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
|
@ -3908,16 +3908,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "7.16.1",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz",
|
||||||
"integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==",
|
"integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "7.16.1",
|
"@typescript-eslint/scope-manager": "7.17.0",
|
||||||
"@typescript-eslint/types": "7.16.1",
|
"@typescript-eslint/types": "7.17.0",
|
||||||
"@typescript-eslint/typescript-estree": "7.16.1"
|
"@typescript-eslint/typescript-estree": "7.17.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || >=20.0.0"
|
"node": "^18.18.0 || >=20.0.0"
|
||||||
|
@ -3931,13 +3931,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "7.16.1",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz",
|
||||||
"integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==",
|
"integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.16.1",
|
"@typescript-eslint/types": "7.17.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -4675,9 +4675,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001642",
|
"version": "1.0.30001643",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
|
||||||
"integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==",
|
"integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -5449,9 +5449,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/detect-gpu": {
|
"node_modules/detect-gpu": {
|
||||||
"version": "5.0.39",
|
"version": "5.0.40",
|
||||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.39.tgz",
|
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.40.tgz",
|
||||||
"integrity": "sha512-qs+7gnNNxsH4RN1IPpQieU2XNO+RhgemuaRhcawiUug6oXb0Glup90H1YGSjslPO30Sw0E4yfjRoGtSEURwVPQ==",
|
"integrity": "sha512-5v4jDN/ERdZZitD29UiLjV9Q9+lDfw2OhEJACIqnvdWulVZCy2K6EwonZ/VKyo4YMqvSIzGIDmojX3jGL3dLpA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"webgl-constants": "^1.1.1"
|
"webgl-constants": "^1.1.1"
|
||||||
|
@ -5567,9 +5567,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.830",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.830.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.1.tgz",
|
||||||
"integrity": "sha512-TrPKKH20HeN0J1LHzsYLs2qwXrp8TF4nHdu4sq61ozGbzMpWhI7iIOPYPPkxeq1azMT9PZ8enPFcftbs/Npcjg==",
|
"integrity": "sha512-FKbOCOQ5QRB3VlIbl1LZQefWIYwszlBloaXcY2rbfpu9ioJnNh3TK03YtIDKDo3WKBi8u+YV4+Fn2CkEozgf4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
@ -5736,9 +5736,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-react-refresh": {
|
"node_modules/eslint-plugin-react-refresh": {
|
||||||
"version": "0.4.8",
|
"version": "0.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.9.tgz",
|
||||||
"integrity": "sha512-MIKAclwaDFIiYtVBLzDdm16E+Ty4GwhB6wZlCAG1R3Ur+F9Qbo6PRxpA5DK7XtDgm+WlCoAY2WxAwqhmIDHg6Q==",
|
"integrity": "sha512-QK49YrBAo5CLNLseZ7sZgvgTy21E6NEw22eZqc4teZfH8pxV3yXc9XXOYfUI6JNpw7mfHNkAeWtBxrTyykB6HA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@ -6325,9 +6325,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/framer-motion": {
|
"node_modules/framer-motion": {
|
||||||
"version": "11.3.8",
|
"version": "11.3.17",
|
||||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.17.tgz",
|
||||||
"integrity": "sha512-1D+RDTsIp4Rz2dq/oToqSEc9idEQwgBRQyBq4rGpFba+0Z+GCbj9z1s0+ikFbanWe3YJ0SqkNlDe08GcpFGj5A==",
|
"integrity": "sha512-LZcckvZL8Rjod03bud8LQcp+R0PLmWIlOSu+NVc+v6Uh43fQr4IBsEAX7sSn7CdBQ1L0fZ/IqSXZVPnGFSMxHw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
|
@ -6866,9 +6866,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/import-local": {
|
"node_modules/import-local": {
|
||||||
"version": "3.1.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
||||||
"integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
|
"integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -9499,9 +9499,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.17",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||||
"integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==",
|
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
@ -9925,9 +9925,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.39",
|
"version": "8.4.40",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
|
||||||
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -10041,9 +10041,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-load-config/node_modules/yaml": {
|
"node_modules/postcss-load-config/node_modules/yaml": {
|
||||||
"version": "2.4.5",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
|
||||||
"integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
|
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -10054,21 +10054,27 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-nested": {
|
"node_modules/postcss-nested": {
|
||||||
"version": "6.0.1",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||||
"integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
|
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"postcss-selector-parser": "^6.0.11"
|
"postcss-selector-parser": "^6.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0"
|
"node": ">=12.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/postcss/"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"postcss": "^8.2.14"
|
"postcss": "^8.2.14"
|
||||||
}
|
}
|
||||||
|
@ -10776,9 +10782,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.18.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.0.tgz",
|
||||||
"integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==",
|
"integrity": "sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -10792,22 +10798,22 @@
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.18.1",
|
"@rollup/rollup-android-arm-eabi": "4.19.0",
|
||||||
"@rollup/rollup-android-arm64": "4.18.1",
|
"@rollup/rollup-android-arm64": "4.19.0",
|
||||||
"@rollup/rollup-darwin-arm64": "4.18.1",
|
"@rollup/rollup-darwin-arm64": "4.19.0",
|
||||||
"@rollup/rollup-darwin-x64": "4.18.1",
|
"@rollup/rollup-darwin-x64": "4.19.0",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.18.1",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.19.0",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.18.1",
|
"@rollup/rollup-linux-arm-musleabihf": "4.19.0",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.18.1",
|
"@rollup/rollup-linux-arm64-gnu": "4.19.0",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.18.1",
|
"@rollup/rollup-linux-arm64-musl": "4.19.0",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.18.1",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.19.0",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.18.1",
|
"@rollup/rollup-linux-riscv64-gnu": "4.19.0",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.18.1",
|
"@rollup/rollup-linux-s390x-gnu": "4.19.0",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.18.1",
|
"@rollup/rollup-linux-x64-gnu": "4.19.0",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.18.1",
|
"@rollup/rollup-linux-x64-musl": "4.19.0",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.18.1",
|
"@rollup/rollup-win32-arm64-msvc": "4.19.0",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.18.1",
|
"@rollup/rollup-win32-ia32-msvc": "4.19.0",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.18.1",
|
"@rollup/rollup-win32-x64-msvc": "4.19.0",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11346,9 +11352,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.6",
|
"version": "3.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz",
|
||||||
"integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==",
|
"integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -11708,9 +11714,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.5.3",
|
"version": "5.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||||
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -11849,9 +11855,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.3.4",
|
"version": "5.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
||||||
"integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==",
|
"integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"@uiw/react-codemirror": "^4.23.0",
|
"@uiw/react-codemirror": "^4.23.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^11.3.8",
|
"framer-motion": "^11.3.17",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
@ -40,24 +40,24 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lezer/generator": "^1.7.1",
|
"@lezer/generator": "^1.7.1",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^20.14.11",
|
"@types/node": "^20.14.12",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||||
"@typescript-eslint/parser": "^7.16.1",
|
"@typescript-eslint/parser": "^7.17.0",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-react-refresh": "^0.4.8",
|
"eslint-plugin-react-refresh": "^0.4.9",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-tsdoc": "^0.3.0",
|
"eslint-plugin-tsdoc": "^0.3.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.40",
|
||||||
"tailwindcss": "^3.4.6",
|
"tailwindcss": "^3.4.7",
|
||||||
"ts-jest": "^29.2.3",
|
"ts-jest": "^29.2.3",
|
||||||
"typescript": "^5.5.3",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.3.4"
|
"vite": "^5.3.5"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "ts-jest",
|
"preset": "ts-jest",
|
||||||
|
|
|
@ -1,577 +0,0 @@
|
||||||
/**
|
|
||||||
* Module: API for backend communications.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import { type ErrorData } from '@/components/info/InfoError';
|
|
||||||
import { ILexemeData, ITextRequest, ITextResult, IWordFormPlain } from '@/models/language';
|
|
||||||
import { ILibraryItem, ILibraryUpdateData, ITargetAccessPolicy, ITargetLocation, IVersionData } from '@/models/library';
|
|
||||||
import { ILibraryCreateData } from '@/models/library';
|
|
||||||
import {
|
|
||||||
ICstSubstituteData,
|
|
||||||
IOperationCreateData,
|
|
||||||
IOperationCreatedResponse,
|
|
||||||
IOperationSchemaData,
|
|
||||||
IPositionsData,
|
|
||||||
ITargetOperation
|
|
||||||
} from '@/models/oss';
|
|
||||||
import {
|
|
||||||
IConstituentaList,
|
|
||||||
IConstituentaMeta,
|
|
||||||
ICstCreateData,
|
|
||||||
ICstCreatedResponse,
|
|
||||||
ICstMovetoData,
|
|
||||||
ICstRenameData,
|
|
||||||
ICstUpdateData,
|
|
||||||
IInlineSynthesisData,
|
|
||||||
IProduceStructureResponse,
|
|
||||||
IRSFormCloneData,
|
|
||||||
IRSFormData,
|
|
||||||
IRSFormUploadData,
|
|
||||||
ITargetCst,
|
|
||||||
IVersionCreatedResponse
|
|
||||||
} from '@/models/rsform';
|
|
||||||
import { IExpressionParse, IRSExpression } from '@/models/rslang';
|
|
||||||
import {
|
|
||||||
ICurrentUser,
|
|
||||||
IPasswordTokenData,
|
|
||||||
IRequestPasswordData,
|
|
||||||
IResetPasswordData,
|
|
||||||
ITargetUser,
|
|
||||||
ITargetUsers,
|
|
||||||
IUserInfo,
|
|
||||||
IUserLoginData,
|
|
||||||
IUserProfile,
|
|
||||||
IUserSignupData,
|
|
||||||
IUserUpdateData,
|
|
||||||
IUserUpdatePassword
|
|
||||||
} from '@/models/user';
|
|
||||||
import { buildConstants } from '@/utils/buildConstants';
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
xsrfCookieName: 'csrftoken',
|
|
||||||
xsrfHeaderName: 'x-csrftoken',
|
|
||||||
baseURL: `${buildConstants.backend}`,
|
|
||||||
withCredentials: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const axiosInstance = axios.create(defaultOptions);
|
|
||||||
axiosInstance.interceptors.request.use(config => {
|
|
||||||
const token = document.cookie
|
|
||||||
.split('; ')
|
|
||||||
.find(row => row.startsWith('csrftoken='))
|
|
||||||
?.split('=')[1];
|
|
||||||
if (token) {
|
|
||||||
config.headers['x-csrftoken'] = token;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
});
|
|
||||||
|
|
||||||
// ================ Data transfer types ================
|
|
||||||
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
|
|
||||||
|
|
||||||
interface IFrontRequest<RequestData, ResponseData> {
|
|
||||||
data?: RequestData;
|
|
||||||
onSuccess?: DataCallback<ResponseData>;
|
|
||||||
onError?: (error: ErrorData) => void;
|
|
||||||
setLoading?: (loading: boolean) => void;
|
|
||||||
showError?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FrontPush<DataType> extends IFrontRequest<DataType, undefined> {
|
|
||||||
data: DataType;
|
|
||||||
}
|
|
||||||
export interface FrontPull<DataType> extends IFrontRequest<undefined, DataType> {
|
|
||||||
onSuccess: DataCallback<DataType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FrontExchange<RequestData, ResponseData> extends IFrontRequest<RequestData, ResponseData> {
|
|
||||||
data: RequestData;
|
|
||||||
onSuccess: DataCallback<ResponseData>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FrontAction extends IFrontRequest<undefined, undefined> {}
|
|
||||||
|
|
||||||
interface IAxiosRequest<RequestData, ResponseData> {
|
|
||||||
endpoint: string;
|
|
||||||
request: IFrontRequest<RequestData, ResponseData>;
|
|
||||||
options?: AxiosRequestConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== API ====================
|
|
||||||
export function getAuth(request: FrontPull<ICurrentUser>) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/users/api/auth`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postLogin(request: FrontPush<IUserLoginData>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/users/api/login',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postLogout(request: FrontAction) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/users/api/logout',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postSignup(request: FrontExchange<IUserSignupData, IUserProfile>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/users/api/signup',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getProfile(request: FrontPull<IUserProfile>) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: '/users/api/profile',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchProfile(request: FrontExchange<IUserUpdateData, IUserProfile>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: '/users/api/profile',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchPassword(request: FrontPush<IUserUpdatePassword>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: '/users/api/change-password',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postRequestPasswordReset(request: FrontPush<IRequestPasswordData>) {
|
|
||||||
// title: 'Request password reset',
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/users/api/password-reset',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postValidatePasswordToken(request: FrontPush<IPasswordTokenData>) {
|
|
||||||
// title: 'Validate password token',
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/users/api/password-reset/validate',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postResetPassword(request: FrontPush<IResetPasswordData>) {
|
|
||||||
// title: 'Reset password',
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/users/api/password-reset/confirm',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
|
||||||
// title: 'Active users list',
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: '/users/api/active-users',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLibrary(request: FrontPull<ILibraryItem[]>) {
|
|
||||||
// title: 'Available LibraryItems list',
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: '/api/library/active',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAdminLibrary(request: FrontPull<ILibraryItem[]>) {
|
|
||||||
// title: 'All LibraryItems list',
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: '/api/library/all',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTemplates(request: FrontPull<ILibraryItem[]>) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: '/api/library/templates',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postRSFormFromFile(request: FrontExchange<ILibraryCreateData, ILibraryItem>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/api/rsforms/create-detailed',
|
|
||||||
request: request,
|
|
||||||
options: {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateLibraryItem(request: FrontExchange<ILibraryCreateData, ILibraryItem>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/api/library',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCloneData, IRSFormData>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/library/${target}/clone`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRSFormDetails(target: string, version: string, request: FrontPull<IRSFormData>) {
|
|
||||||
if (!version) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/rsforms/${target}/details`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/rsforms/${target}/versions/${version}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchLibraryItem(target: string, request: FrontExchange<ILibraryUpdateData, ILibraryItem>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteLibraryItem(target: string, request: FrontAction) {
|
|
||||||
AxiosDelete({
|
|
||||||
endpoint: `/api/library/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSetOwner(target: string, request: FrontPush<ITargetUser>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/set-owner`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSetAccessPolicy(target: string, request: FrontPush<ITargetAccessPolicy>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/set-access-policy`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSetLocation(target: string, request: FrontPush<ITargetLocation>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/set-location`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchEditorsAdd(target: string, request: FrontPush<ITargetUser>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/editors-add`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchEditorsRemove(target: string, request: FrontPush<ITargetUser>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/editors-remove`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchEditorsSet(target: string, request: FrontPush<ITargetUsers>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/editors-set`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postSubscribe(target: string, request: FrontAction) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/library/${target}/subscribe`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteUnsubscribe(target: string, request: FrontAction) {
|
|
||||||
AxiosDelete({
|
|
||||||
endpoint: `/api/library/${target}/unsubscribe`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTRSFile(target: string, version: string, request: FrontPull<Blob>) {
|
|
||||||
if (!version) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/rsforms/${target}/export-trs`,
|
|
||||||
request: request,
|
|
||||||
options: { responseType: 'blob' }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/versions/${version}/export-file`,
|
|
||||||
request: request,
|
|
||||||
options: { responseType: 'blob' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateConstituenta(schema: string, request: FrontExchange<ICstCreateData, ICstCreatedResponse>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/rsforms/${schema}/cst-create`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/cst-delete-multiple`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchConstituenta(target: string, request: FrontExchange<ICstUpdateData, IConstituentaMeta>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/constituents/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchRenameConstituenta(schema: string, request: FrontExchange<ICstRenameData, ICstCreatedResponse>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/cst-rename`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchProduceStructure(schema: string, request: FrontExchange<ITargetCst, IProduceStructureResponse>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/cst-produce-structure`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSubstituteConstituents(schema: string, request: FrontExchange<ICstSubstituteData, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/cst-substitute`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchMoveConstituenta(schema: string, request: FrontExchange<ICstMovetoData, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/cst-moveto`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCheckExpression(schema: string, request: FrontExchange<IRSExpression, IExpressionParse>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/rsforms/${schema}/check`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchResetAliases(target: string, request: FrontPull<IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${target}/reset-aliases`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchRestoreOrder(target: string, request: FrontPull<IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${target}/restore-order`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchUploadTRS(target: string, request: FrontExchange<IRSFormUploadData, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${target}/load-trs`,
|
|
||||||
request: request,
|
|
||||||
options: {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function patchInlineSynthesis(request: FrontExchange<IInlineSynthesisData, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/operations/inline-synthesis`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getOssDetails(target: string, request: FrontPull<IOperationSchemaData>) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/oss/${target}/details`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchUpdatePositions(schema: string, request: FrontPush<IPositionsData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/oss/${schema}/update-positions`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateOperation(
|
|
||||||
schema: string,
|
|
||||||
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
|
||||||
) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/oss/${schema}/create-operation`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchDeleteOperation(schema: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/oss/${schema}/delete-operation`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postInflectText(request: FrontExchange<IWordFormPlain, ITextResult>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/cctext/inflect`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postParseText(request: FrontExchange<ITextRequest, ITextResult>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/cctext/parse`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postGenerateLexeme(request: FrontExchange<ITextRequest, ILexemeData>) {
|
|
||||||
// title: `Parse text ${request.data.text}`,
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/cctext/generate-lexeme`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateVersion(target: string, request: FrontExchange<IVersionData, IVersionCreatedResponse>) {
|
|
||||||
// title: `Create version for RSForm id=${target}`,
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/rsforms/${target}/versions/create`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchVersion(target: string, request: FrontPush<IVersionData>) {
|
|
||||||
// title: `Version id=${target}`,
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/versions/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchRestoreVersion(target: string, request: FrontPull<IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/versions/${target}/restore`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteVersion(target: string, request: FrontAction) {
|
|
||||||
AxiosDelete({
|
|
||||||
endpoint: `/api/versions/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============ Helper functions =============
|
|
||||||
function AxiosGet<ResponseData>({ endpoint, request, options }: IAxiosRequest<undefined, ResponseData>) {
|
|
||||||
if (request.setLoading) request.setLoading(true);
|
|
||||||
axiosInstance
|
|
||||||
.get<ResponseData>(endpoint, options)
|
|
||||||
.then(response => {
|
|
||||||
if (request.setLoading) request.setLoading(false);
|
|
||||||
if (request.onSuccess) request.onSuccess(response.data);
|
|
||||||
})
|
|
||||||
.catch((error: Error | AxiosError) => {
|
|
||||||
if (request.setLoading) request.setLoading(false);
|
|
||||||
if (request.showError) toast.error(error.message);
|
|
||||||
if (request.onError) request.onError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function AxiosPost<RequestData, ResponseData>({
|
|
||||||
endpoint,
|
|
||||||
request,
|
|
||||||
options
|
|
||||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
|
||||||
if (request.setLoading) request.setLoading(true);
|
|
||||||
axiosInstance
|
|
||||||
.post<ResponseData>(endpoint, request.data, options)
|
|
||||||
.then(response => {
|
|
||||||
if (request.setLoading) request.setLoading(false);
|
|
||||||
if (request.onSuccess) request.onSuccess(response.data);
|
|
||||||
})
|
|
||||||
.catch((error: Error | AxiosError) => {
|
|
||||||
if (request.setLoading) request.setLoading(false);
|
|
||||||
if (request.showError) toast.error(error.message);
|
|
||||||
if (request.onError) request.onError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function AxiosDelete<RequestData, ResponseData>({
|
|
||||||
endpoint,
|
|
||||||
request,
|
|
||||||
options
|
|
||||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
|
||||||
if (request.setLoading) request.setLoading(true);
|
|
||||||
axiosInstance
|
|
||||||
.delete<ResponseData>(endpoint, options)
|
|
||||||
.then(response => {
|
|
||||||
if (request.setLoading) request.setLoading(false);
|
|
||||||
if (request.onSuccess) request.onSuccess(response.data);
|
|
||||||
})
|
|
||||||
.catch((error: Error | AxiosError) => {
|
|
||||||
if (request.setLoading) request.setLoading(false);
|
|
||||||
if (request.showError) toast.error(error.message);
|
|
||||||
if (request.onError) request.onError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function AxiosPatch<RequestData, ResponseData>({
|
|
||||||
endpoint,
|
|
||||||
request,
|
|
||||||
options
|
|
||||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
|
||||||
if (request.setLoading) request.setLoading(true);
|
|
||||||
axiosInstance
|
|
||||||
.patch<ResponseData>(endpoint, request.data, options)
|
|
||||||
.then(response => {
|
|
||||||
if (request.setLoading) request.setLoading(false);
|
|
||||||
if (request.onSuccess) request.onSuccess(response.data);
|
|
||||||
return response.data;
|
|
||||||
})
|
|
||||||
.catch((error: Error | AxiosError) => {
|
|
||||||
if (request.setLoading) request.setLoading(false);
|
|
||||||
if (request.showError) toast.error(error.message);
|
|
||||||
if (request.onError) request.onError(error);
|
|
||||||
});
|
|
||||||
}
|
|
25
rsconcept/frontend/src/backend/apiConfiguration.ts
Normal file
25
rsconcept/frontend/src/backend/apiConfiguration.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Module: communication setup.
|
||||||
|
*/
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import { buildConstants } from '@/utils/buildConstants';
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
xsrfCookieName: 'csrftoken',
|
||||||
|
xsrfHeaderName: 'x-csrftoken',
|
||||||
|
baseURL: `${buildConstants.backend}`,
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export const axiosInstance = axios.create(defaultOptions);
|
||||||
|
axiosInstance.interceptors.request.use(config => {
|
||||||
|
const token = document.cookie
|
||||||
|
.split('; ')
|
||||||
|
.find(row => row.startsWith('csrftoken='))
|
||||||
|
?.split('=')[1];
|
||||||
|
if (token) {
|
||||||
|
config.headers['x-csrftoken'] = token;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
114
rsconcept/frontend/src/backend/apiTransport.ts
Normal file
114
rsconcept/frontend/src/backend/apiTransport.ts
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/**
|
||||||
|
* Module: generic API for backend REST communications.
|
||||||
|
*/
|
||||||
|
import { AxiosError, AxiosRequestConfig } from 'axios';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import { ErrorData } from '@/components/info/InfoError';
|
||||||
|
|
||||||
|
import { axiosInstance } from './apiConfiguration';
|
||||||
|
|
||||||
|
// ================ Data transfer types ================
|
||||||
|
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
|
||||||
|
|
||||||
|
export interface IFrontRequest<RequestData, ResponseData> {
|
||||||
|
data?: RequestData;
|
||||||
|
onSuccess?: DataCallback<ResponseData>;
|
||||||
|
onError?: (error: ErrorData) => void;
|
||||||
|
setLoading?: (loading: boolean) => void;
|
||||||
|
showError?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrontPush<DataType> extends IFrontRequest<DataType, undefined> {
|
||||||
|
data: DataType;
|
||||||
|
}
|
||||||
|
export interface FrontPull<DataType> extends IFrontRequest<undefined, DataType> {
|
||||||
|
onSuccess: DataCallback<DataType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrontExchange<RequestData, ResponseData> extends IFrontRequest<RequestData, ResponseData> {
|
||||||
|
data: RequestData;
|
||||||
|
onSuccess: DataCallback<ResponseData>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrontAction extends IFrontRequest<undefined, undefined> {}
|
||||||
|
|
||||||
|
export interface IAxiosRequest<RequestData, ResponseData> {
|
||||||
|
endpoint: string;
|
||||||
|
request: IFrontRequest<RequestData, ResponseData>;
|
||||||
|
options?: AxiosRequestConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================ Transport API calls ================
|
||||||
|
export function AxiosGet<ResponseData>({ endpoint, request, options }: IAxiosRequest<undefined, ResponseData>) {
|
||||||
|
if (request.setLoading) request.setLoading(true);
|
||||||
|
axiosInstance
|
||||||
|
.get<ResponseData>(endpoint, options)
|
||||||
|
.then(response => {
|
||||||
|
if (request.setLoading) request.setLoading(false);
|
||||||
|
if (request.onSuccess) request.onSuccess(response.data);
|
||||||
|
})
|
||||||
|
.catch((error: Error | AxiosError) => {
|
||||||
|
if (request.setLoading) request.setLoading(false);
|
||||||
|
if (request.showError) toast.error(error.message);
|
||||||
|
if (request.onError) request.onError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AxiosPost<RequestData, ResponseData>({
|
||||||
|
endpoint,
|
||||||
|
request,
|
||||||
|
options
|
||||||
|
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||||
|
if (request.setLoading) request.setLoading(true);
|
||||||
|
axiosInstance
|
||||||
|
.post<ResponseData>(endpoint, request.data, options)
|
||||||
|
.then(response => {
|
||||||
|
if (request.setLoading) request.setLoading(false);
|
||||||
|
if (request.onSuccess) request.onSuccess(response.data);
|
||||||
|
})
|
||||||
|
.catch((error: Error | AxiosError) => {
|
||||||
|
if (request.setLoading) request.setLoading(false);
|
||||||
|
if (request.showError) toast.error(error.message);
|
||||||
|
if (request.onError) request.onError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AxiosDelete<RequestData, ResponseData>({
|
||||||
|
endpoint,
|
||||||
|
request,
|
||||||
|
options
|
||||||
|
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||||
|
if (request.setLoading) request.setLoading(true);
|
||||||
|
axiosInstance
|
||||||
|
.delete<ResponseData>(endpoint, options)
|
||||||
|
.then(response => {
|
||||||
|
if (request.setLoading) request.setLoading(false);
|
||||||
|
if (request.onSuccess) request.onSuccess(response.data);
|
||||||
|
})
|
||||||
|
.catch((error: Error | AxiosError) => {
|
||||||
|
if (request.setLoading) request.setLoading(false);
|
||||||
|
if (request.showError) toast.error(error.message);
|
||||||
|
if (request.onError) request.onError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AxiosPatch<RequestData, ResponseData>({
|
||||||
|
endpoint,
|
||||||
|
request,
|
||||||
|
options
|
||||||
|
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||||
|
if (request.setLoading) request.setLoading(true);
|
||||||
|
axiosInstance
|
||||||
|
.patch<ResponseData>(endpoint, request.data, options)
|
||||||
|
.then(response => {
|
||||||
|
if (request.setLoading) request.setLoading(false);
|
||||||
|
if (request.onSuccess) request.onSuccess(response.data);
|
||||||
|
return response.data;
|
||||||
|
})
|
||||||
|
.catch((error: Error | AxiosError) => {
|
||||||
|
if (request.setLoading) request.setLoading(false);
|
||||||
|
if (request.showError) toast.error(error.message);
|
||||||
|
if (request.onError) request.onError(error);
|
||||||
|
});
|
||||||
|
}
|
28
rsconcept/frontend/src/backend/cctext.ts
Normal file
28
rsconcept/frontend/src/backend/cctext.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* Endpoints: cctext.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ILexemeData, ITextRequest, ITextResult, IWordFormPlain } from '@/models/language';
|
||||||
|
|
||||||
|
import { AxiosPost, FrontExchange } from './apiTransport';
|
||||||
|
|
||||||
|
export function postInflectText(request: FrontExchange<IWordFormPlain, ITextResult>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/cctext/inflect`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postParseText(request: FrontExchange<ITextRequest, ITextResult>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/cctext/parse`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postGenerateLexeme(request: FrontExchange<ITextRequest, ILexemeData>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/cctext/generate-lexeme`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
14
rsconcept/frontend/src/backend/constituents.ts
Normal file
14
rsconcept/frontend/src/backend/constituents.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Endpoints: constituents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IConstituentaMeta, ICstUpdateData } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { AxiosPatch, FrontExchange } from './apiTransport';
|
||||||
|
|
||||||
|
export function patchConstituenta(target: string, request: FrontExchange<ICstUpdateData, IConstituentaMeta>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/constituents/${target}`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
123
rsconcept/frontend/src/backend/library.ts
Normal file
123
rsconcept/frontend/src/backend/library.ts
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/**
|
||||||
|
* Endpoints: library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ILibraryCreateData,
|
||||||
|
ILibraryItem,
|
||||||
|
ILibraryUpdateData,
|
||||||
|
ITargetAccessPolicy,
|
||||||
|
ITargetLocation,
|
||||||
|
IVersionData
|
||||||
|
} from '@/models/library';
|
||||||
|
import { IRSFormCloneData, IRSFormData, IVersionCreatedResponse } from '@/models/rsform';
|
||||||
|
import { ITargetUser, ITargetUsers } from '@/models/user';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AxiosDelete,
|
||||||
|
AxiosGet,
|
||||||
|
AxiosPatch,
|
||||||
|
AxiosPost,
|
||||||
|
FrontAction,
|
||||||
|
FrontExchange,
|
||||||
|
FrontPull,
|
||||||
|
FrontPush
|
||||||
|
} from './apiTransport';
|
||||||
|
|
||||||
|
export function getLibrary(request: FrontPull<ILibraryItem[]>) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: '/api/library/active',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAdminLibrary(request: FrontPull<ILibraryItem[]>) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: '/api/library/all',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTemplates(request: FrontPull<ILibraryItem[]>) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: '/api/library/templates',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postCreateLibraryItem(request: FrontExchange<ILibraryCreateData, ILibraryItem>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: '/api/library',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCloneData, IRSFormData>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/library/${target}/clone`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchLibraryItem(target: string, request: FrontExchange<ILibraryUpdateData, ILibraryItem>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/library/${target}`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteLibraryItem(target: string, request: FrontAction) {
|
||||||
|
AxiosDelete({
|
||||||
|
endpoint: `/api/library/${target}`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchSetOwner(target: string, request: FrontPush<ITargetUser>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/library/${target}/set-owner`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchSetAccessPolicy(target: string, request: FrontPush<ITargetAccessPolicy>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/library/${target}/set-access-policy`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchSetLocation(target: string, request: FrontPush<ITargetLocation>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/library/${target}/set-location`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchSetEditors(target: string, request: FrontPush<ITargetUsers>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/library/${target}/set-editors`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postSubscribe(target: string, request: FrontAction) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/library/${target}/subscribe`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteUnsubscribe(target: string, request: FrontAction) {
|
||||||
|
AxiosDelete({
|
||||||
|
endpoint: `/api/library/${target}/unsubscribe`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postCreateVersion(target: string, request: FrontExchange<IVersionData, IVersionCreatedResponse>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/library/${target}/create-version`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
14
rsconcept/frontend/src/backend/operations.ts
Normal file
14
rsconcept/frontend/src/backend/operations.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Endpoints: operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IInlineSynthesisData, IRSFormData } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { AxiosPatch, FrontExchange } from './apiTransport';
|
||||||
|
|
||||||
|
export function patchInlineSynthesis(request: FrontExchange<IInlineSynthesisData, IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/operations/inline-synthesis`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
44
rsconcept/frontend/src/backend/oss.ts
Normal file
44
rsconcept/frontend/src/backend/oss.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Endpoints: oss.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
IOperationCreateData,
|
||||||
|
IOperationCreatedResponse,
|
||||||
|
IOperationSchemaData,
|
||||||
|
IPositionsData,
|
||||||
|
ITargetOperation
|
||||||
|
} from '@/models/oss';
|
||||||
|
|
||||||
|
import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport';
|
||||||
|
|
||||||
|
export function getOssDetails(target: string, request: FrontPull<IOperationSchemaData>) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: `/api/oss/${target}/details`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchUpdatePositions(schema: string, request: FrontPush<IPositionsData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/oss/${schema}/update-positions`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postCreateOperation(
|
||||||
|
schema: string,
|
||||||
|
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
||||||
|
) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/oss/${schema}/create-operation`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchDeleteOperation(schema: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/oss/${schema}/delete-operation`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
137
rsconcept/frontend/src/backend/rsforms.ts
Normal file
137
rsconcept/frontend/src/backend/rsforms.ts
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
* Endpoints: rsforms.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ILibraryCreateData, ILibraryItem } from '@/models/library';
|
||||||
|
import { ICstSubstituteData } from '@/models/oss';
|
||||||
|
import {
|
||||||
|
IConstituentaList,
|
||||||
|
ICstCreateData,
|
||||||
|
ICstCreatedResponse,
|
||||||
|
ICstMovetoData,
|
||||||
|
ICstRenameData,
|
||||||
|
IProduceStructureResponse,
|
||||||
|
IRSFormData,
|
||||||
|
IRSFormUploadData,
|
||||||
|
ITargetCst
|
||||||
|
} from '@/models/rsform';
|
||||||
|
import { IExpressionParse, IRSExpression } from '@/models/rslang';
|
||||||
|
|
||||||
|
import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull } from './apiTransport';
|
||||||
|
|
||||||
|
export function postRSFormFromFile(request: FrontExchange<ILibraryCreateData, ILibraryItem>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: '/api/rsforms/create-detailed',
|
||||||
|
request: request,
|
||||||
|
options: {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRSFormDetails(target: string, version: string, request: FrontPull<IRSFormData>) {
|
||||||
|
if (!version) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: `/api/rsforms/${target}/details`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: `/api/library/${target}/versions/${version}`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTRSFile(target: string, version: string, request: FrontPull<Blob>) {
|
||||||
|
if (!version) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: `/api/rsforms/${target}/export-trs`,
|
||||||
|
request: request,
|
||||||
|
options: { responseType: 'blob' }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: `/api/versions/${version}/export-file`,
|
||||||
|
request: request,
|
||||||
|
options: { responseType: 'blob' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postCreateConstituenta(schema: string, request: FrontExchange<ICstCreateData, ICstCreatedResponse>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/rsforms/${schema}/create-cst`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/rsforms/${schema}/delete-multiple-cst`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchRenameConstituenta(schema: string, request: FrontExchange<ICstRenameData, ICstCreatedResponse>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/rsforms/${schema}/rename-cst`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchProduceStructure(schema: string, request: FrontExchange<ITargetCst, IProduceStructureResponse>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/rsforms/${schema}/produce-structure`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchSubstituteConstituents(schema: string, request: FrontExchange<ICstSubstituteData, IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/rsforms/${schema}/substitute`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchMoveConstituenta(schema: string, request: FrontExchange<ICstMovetoData, IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/rsforms/${schema}/move-cst`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postCheckExpression(schema: string, request: FrontExchange<IRSExpression, IExpressionParse>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/rsforms/${schema}/check`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchResetAliases(target: string, request: FrontPull<IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/rsforms/${target}/reset-aliases`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchRestoreOrder(target: string, request: FrontPull<IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/rsforms/${target}/restore-order`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchUploadTRS(target: string, request: FrontExchange<IRSFormUploadData, IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/rsforms/${target}/load-trs`,
|
||||||
|
request: request,
|
||||||
|
options: {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
99
rsconcept/frontend/src/backend/users.ts
Normal file
99
rsconcept/frontend/src/backend/users.ts
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* Endpoints: users.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ICurrentUser,
|
||||||
|
IPasswordTokenData,
|
||||||
|
IRequestPasswordData,
|
||||||
|
IResetPasswordData,
|
||||||
|
IUserInfo,
|
||||||
|
IUserLoginData,
|
||||||
|
IUserProfile,
|
||||||
|
IUserSignupData,
|
||||||
|
IUserUpdateData,
|
||||||
|
IUserUpdatePassword
|
||||||
|
} from '@/models/user';
|
||||||
|
|
||||||
|
import { AxiosGet, AxiosPatch, AxiosPost, FrontAction, FrontExchange, FrontPull, FrontPush } from './apiTransport';
|
||||||
|
|
||||||
|
export function getAuth(request: FrontPull<ICurrentUser>) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: `/users/api/auth`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postLogin(request: FrontPush<IUserLoginData>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: '/users/api/login',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postLogout(request: FrontAction) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: '/users/api/logout',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postSignup(request: FrontExchange<IUserSignupData, IUserProfile>) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: '/users/api/signup',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProfile(request: FrontPull<IUserProfile>) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: '/users/api/profile',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchProfile(request: FrontExchange<IUserUpdateData, IUserProfile>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: '/users/api/profile',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchPassword(request: FrontPush<IUserUpdatePassword>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: '/users/api/change-password',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postRequestPasswordReset(request: FrontPush<IRequestPasswordData>) {
|
||||||
|
// title: 'Request password reset',
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: '/users/api/password-reset',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postValidatePasswordToken(request: FrontPush<IPasswordTokenData>) {
|
||||||
|
// title: 'Validate password token',
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: '/users/api/password-reset/validate',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postResetPassword(request: FrontPush<IResetPasswordData>) {
|
||||||
|
// title: 'Reset password',
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: '/users/api/password-reset/confirm',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
||||||
|
// title: 'Active users list',
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: '/users/api/active-users',
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
30
rsconcept/frontend/src/backend/versions.ts
Normal file
30
rsconcept/frontend/src/backend/versions.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Endpoints: versions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IVersionData } from '@/models/library';
|
||||||
|
import { IRSFormData } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { AxiosDelete, AxiosPatch, FrontAction, FrontPull, FrontPush } from './apiTransport';
|
||||||
|
|
||||||
|
export function patchVersion(target: string, request: FrontPush<IVersionData>) {
|
||||||
|
// title: `Version id=${target}`,
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/versions/${target}`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchRestoreVersion(target: string, request: FrontPull<IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/versions/${target}/restore`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteVersion(target: string, request: FrontAction) {
|
||||||
|
AxiosDelete({
|
||||||
|
endpoint: `/api/versions/${target}`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
36
rsconcept/frontend/src/components/info/TooltipOperation.tsx
Normal file
36
rsconcept/frontend/src/components/info/TooltipOperation.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import Tooltip from '@/components/ui/Tooltip';
|
||||||
|
import { OssNodeInternal } from '@/models/miscellaneous';
|
||||||
|
import { labelOperationType } from '@/utils/labels';
|
||||||
|
|
||||||
|
interface TooltipOperationProps {
|
||||||
|
node: OssNodeInternal;
|
||||||
|
anchor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
||||||
|
return (
|
||||||
|
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense my-3'>
|
||||||
|
<h2>{node.data.operation.alias}</h2>
|
||||||
|
<p>
|
||||||
|
<b>Тип:</b> {labelOperationType(node.data.operation.operation_type)}
|
||||||
|
</p>
|
||||||
|
{node.data.operation.title ? (
|
||||||
|
<p>
|
||||||
|
<b>Название: </b>
|
||||||
|
{node.data.operation.title}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
{node.data.operation.comment ? (
|
||||||
|
<p>
|
||||||
|
<b>Комментарий: </b>
|
||||||
|
{node.data.operation.comment}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
<p>
|
||||||
|
<b>Положение:</b> [{node.xPos}, {node.yPos}]
|
||||||
|
</p>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TooltipOperation;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user