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)
|
||||
|
||||
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
|
||||
|
||||
- feel free to open issues, discussion topics, contact maintainer directly
|
||||
- use Test config in VSCode to run tests before pushing commits / requests
|
||||
- use github actions to setup linter checks and test builds
|
||||
- use conventional commits to describe changes
|
||||
|
||||
## ✨ Frontend [Vite + React + Typescript]
|
||||
|
||||
|
@ -138,6 +139,14 @@ This readme file is used mostly to document project dependencies
|
|||
|
||||
# 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+)
|
||||
|
||||
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. '''
|
||||
item: ForeignKey = ForeignKey(
|
||||
verbose_name='Схема',
|
||||
to='rsform.LibraryItem',
|
||||
to='library.LibraryItem',
|
||||
on_delete=CASCADE
|
||||
)
|
||||
editor: ForeignKey = ForeignKey(
|
|
@ -54,7 +54,8 @@ class LibraryItem(Model):
|
|||
item_type: CharField = CharField(
|
||||
verbose_name='Тип',
|
||||
max_length=50,
|
||||
choices=LibraryItemType.choices
|
||||
choices=LibraryItemType.choices,
|
||||
default=LibraryItemType.RSFORM
|
||||
)
|
||||
owner: ForeignKey = ForeignKey(
|
||||
verbose_name='Владелец',
|
||||
|
@ -128,7 +129,30 @@ class LibraryItem(Model):
|
|||
|
||||
@transaction.atomic
|
||||
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)
|
||||
if subscribe:
|
||||
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. '''
|
||||
lib_source: ForeignKey = ForeignKey(
|
||||
verbose_name='Источник',
|
||||
to='rsform.RSForm',
|
||||
to='library.LibraryItem',
|
||||
on_delete=CASCADE,
|
||||
null=True
|
||||
)
|
|
@ -18,7 +18,7 @@ class Subscription(Model):
|
|||
)
|
||||
item: ForeignKey = ForeignKey(
|
||||
verbose_name='Элемент',
|
||||
to='rsform.LibraryItem',
|
||||
to='library.LibraryItem',
|
||||
on_delete=CASCADE
|
||||
)
|
||||
|
|
@ -14,7 +14,7 @@ class Version(Model):
|
|||
''' Library item version archive. '''
|
||||
item: ForeignKey = ForeignKey(
|
||||
verbose_name='Схема',
|
||||
to='rsform.LibraryItem',
|
||||
to='library.LibraryItem',
|
||||
on_delete=CASCADE
|
||||
)
|
||||
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. '''
|
||||
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):
|
||||
|
@ -10,7 +11,8 @@ class TestEditor(TestCase):
|
|||
def setUp(self):
|
||||
self.user1 = User.objects.create(username='User1')
|
||||
self.user2 = User.objects.create(username='User2')
|
||||
self.item = RSForm.objects.create(
|
||||
self.item = LibraryItem.objects.create(
|
||||
item_type=LibraryItemType.RSFORM,
|
||||
title='Test',
|
||||
alias='КС1',
|
||||
owner=self.user1
|
|
@ -1,15 +1,15 @@
|
|||
''' Testing models: LibraryItem. '''
|
||||
from django.test import TestCase
|
||||
|
||||
from apps.rsform.models import (
|
||||
from apps.library.models import (
|
||||
AccessPolicy,
|
||||
LibraryItem,
|
||||
LibraryItemType,
|
||||
LocationHead,
|
||||
Subscription,
|
||||
User,
|
||||
validate_location
|
||||
)
|
||||
from apps.users.models import User
|
||||
|
||||
|
||||
class TestLibraryItem(TestCase):
|
|
@ -1,7 +1,8 @@
|
|||
''' Testing models: Subscription. '''
|
||||
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):
|
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. '''
|
||||
from rest_framework import status
|
||||
|
||||
from apps.rsform.models import (
|
||||
from apps.library.models import (
|
||||
AccessPolicy,
|
||||
Editor,
|
||||
LibraryItem,
|
||||
LibraryItemType,
|
||||
LibraryTemplate,
|
||||
LocationHead,
|
||||
RSForm,
|
||||
Subscription
|
||||
)
|
||||
from apps.rsform.models import RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
from shared.testing_utils import response_contains
|
||||
|
||||
|
@ -20,16 +20,16 @@ class TestLibraryViewset(EndpointTester):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.owned = RSForm.objects.create(
|
||||
self.owned = LibraryItem.objects.create(
|
||||
title='Test',
|
||||
alias='T1',
|
||||
owner=self.user
|
||||
)
|
||||
self.unowned = RSForm.objects.create(
|
||||
self.unowned = LibraryItem.objects.create(
|
||||
title='Test2',
|
||||
alias='T2'
|
||||
)
|
||||
self.common = RSForm.objects.create(
|
||||
self.common = LibraryItem.objects.create(
|
||||
title='Test3',
|
||||
alias='T3',
|
||||
location=LocationHead.COMMON
|
||||
|
@ -44,12 +44,16 @@ class TestLibraryViewset(EndpointTester):
|
|||
'title': 'Title',
|
||||
'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 = {
|
||||
'item_type': LibraryItemType.OPERATION_SCHEMA,
|
||||
'title': 'Title',
|
||||
'alias': 'alias',
|
||||
'title': 'Title2',
|
||||
'alias': 'alias2',
|
||||
'access_policy': AccessPolicy.PROTECTED,
|
||||
'visible': False,
|
||||
'read_only': True
|
||||
|
@ -179,7 +183,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
self.unowned.refresh_from_db()
|
||||
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):
|
||||
time_update = self.owned.time_update
|
||||
|
||||
|
@ -203,7 +207,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
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):
|
||||
time_update = self.owned.time_update
|
||||
|
||||
|
@ -230,7 +234,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
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):
|
||||
time_update = self.owned.time_update
|
||||
|
||||
|
@ -359,12 +363,13 @@ class TestLibraryViewset(EndpointTester):
|
|||
|
||||
@decl_endpoint('/api/library/{item}/clone', method='post')
|
||||
def test_clone_rsform(self):
|
||||
x12 = self.owned.insert_new(
|
||||
schema = RSForm(self.owned)
|
||||
x12 = schema.insert_new(
|
||||
alias='X12',
|
||||
term_raw='человек',
|
||||
term_resolved='человек'
|
||||
)
|
||||
d2 = self.owned.insert_new(
|
||||
d2 = schema.insert_new(
|
||||
alias='D2',
|
||||
term_raw='@{X12|plur}',
|
||||
term_resolved='люди'
|
|
@ -15,51 +15,72 @@ class TestVersionViews(EndpointTester):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.owned = RSForm.objects.create(title='Test', alias='T1', owner=self.user)
|
||||
self.unowned = RSForm.objects.create(title='Test2', alias='T2')
|
||||
self.owned = RSForm.create(title='Test', alias='T1', owner=self.user)
|
||||
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(
|
||||
alias='X1',
|
||||
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):
|
||||
invalid_data = {'description': 'test'}
|
||||
invalid_id = 1338
|
||||
data = {'version': '1.0.0', 'description': 'test'}
|
||||
|
||||
self.executeNotFound(data=data, schema=invalid_id)
|
||||
self.executeForbidden(data=data, schema=self.unowned.pk)
|
||||
self.executeBadData(data=invalid_data, schema=self.owned.pk)
|
||||
self.executeForbidden(data=data, schema=self.unowned_id)
|
||||
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('schema' in response.data)
|
||||
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):
|
||||
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
||||
invalid_id = version_id + 1337
|
||||
|
||||
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=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.x1.alias = 'X33'
|
||||
self.x1.save()
|
||||
|
||||
response = self.executeOK(schema=self.owned.pk, version=version_id)
|
||||
self.assertNotEqual(response.data['alias'], self.owned.alias)
|
||||
response = self.executeOK(schema=self.owned_id, version=version_id)
|
||||
self.assertNotEqual(response.data['alias'], self.owned.model.alias)
|
||||
self.assertNotEqual(response.data['items'][0]['alias'], self.x1.alias)
|
||||
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')
|
||||
def test_access_version(self):
|
||||
data = {'version': '1.0.0', 'description': 'test'}
|
||||
|
@ -73,7 +94,7 @@ class TestVersionViews(EndpointTester):
|
|||
response = self.executeOK()
|
||||
self.assertEqual(response.data['version'], data['version'])
|
||||
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'}
|
||||
self.method = 'patch'
|
||||
|
@ -95,25 +116,6 @@ class TestVersionViews(EndpointTester):
|
|||
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')
|
||||
def test_export_version(self):
|
||||
invalid_id = 1338
|
||||
|
@ -123,7 +125,7 @@ class TestVersionViews(EndpointTester):
|
|||
response = self.executeOK(version=version_id)
|
||||
self.assertEqual(
|
||||
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 ZipFile(stream, 'r') as zipped_file:
|
||||
|
@ -165,7 +167,7 @@ class TestVersionViews(EndpointTester):
|
|||
|
||||
def _create_version(self, data) -> int:
|
||||
response = self.client.post(
|
||||
f'/api/rsforms/{self.owned.pk}/versions/create',
|
||||
f'/api/library/{self.owned_id}/create-version',
|
||||
data=data, format='json'
|
||||
)
|
||||
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.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 .. import models as m
|
||||
|
@ -49,9 +52,9 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
'set_owner',
|
||||
'set_access_policy',
|
||||
'set_location',
|
||||
'editors_add',
|
||||
'editors_remove',
|
||||
'editors_set'
|
||||
'add_editor',
|
||||
'remove_editor',
|
||||
'set_editors'
|
||||
]:
|
||||
access_level = permissions.ItemOwner
|
||||
elif self.action in [
|
||||
|
@ -73,7 +76,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
tags=['Library'],
|
||||
request=s.LibraryItemCloneSerializer,
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.RSFormParseSerializer,
|
||||
c.HTTP_201_CREATED: RSFormParseSerializer,
|
||||
c.HTTP_400_BAD_REQUEST: None,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
|
@ -88,8 +91,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
if item.item_type != m.LibraryItemType.RSFORM:
|
||||
return Response(status=c.HTTP_400_BAD_REQUEST)
|
||||
|
||||
schema = m.RSForm.objects.get(pk=item.pk)
|
||||
clone = deepcopy(schema)
|
||||
clone = deepcopy(item)
|
||||
clone.pk = None
|
||||
clone.owner = self.request.user
|
||||
clone.title = serializer.validated_data['title']
|
||||
|
@ -103,14 +105,14 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
with transaction.atomic():
|
||||
clone.save()
|
||||
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']:
|
||||
cst.pk = None
|
||||
cst.schema = clone
|
||||
cst.save()
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data=s.RSFormParseSerializer(clone).data
|
||||
data=RSFormParseSerializer(clone).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -127,7 +129,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
def subscribe(self, request: Request, pk):
|
||||
''' Endpoint: Subscribe current user to 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)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -144,7 +146,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
def unsubscribe(self, request: Request, pk):
|
||||
''' Endpoint: Unsubscribe current user from 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)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -184,7 +186,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
item = self._get_item()
|
||||
serializer = s.AccessPolicySerializer(data=request.data)
|
||||
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)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -220,8 +223,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='editors-add')
|
||||
def editors_add(self, request: Request, pk):
|
||||
@action(detail=True, methods=['patch'], url_path='add-editor')
|
||||
def add_editor(self, request: Request, pk):
|
||||
''' Endpoint: Add editor for item. '''
|
||||
item = self._get_item()
|
||||
serializer = s.UserTargetSerializer(data=request.data)
|
||||
|
@ -240,8 +243,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='editors-remove')
|
||||
def editors_remove(self, request: Request, pk):
|
||||
@action(detail=True, methods=['patch'], url_path='remove-editor')
|
||||
def remove_editor(self, request: Request, pk):
|
||||
''' Endpoint: Remove editor for item. '''
|
||||
item = self._get_item()
|
||||
serializer = s.UserTargetSerializer(data=request.data)
|
||||
|
@ -260,8 +263,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='editors-set')
|
||||
def editors_set(self, request: Request, pk):
|
||||
@action(detail=True, methods=['patch'], url_path='set-editors')
|
||||
def set_editors(self, request: Request, pk):
|
||||
''' Endpoint: Set list of editors for item. '''
|
||||
item = self._get_item()
|
||||
serializer = s.UsersListSerializer(data=request.data)
|
||||
|
@ -286,7 +289,7 @@ class LibraryActiveView(generics.ListAPIView):
|
|||
.filter(is_public) \
|
||||
.filter(common_location).order_by('-time_update')
|
||||
else:
|
||||
user = cast(m.User, self.request.user)
|
||||
user = cast(User, self.request.user)
|
||||
# pylint: disable=unsupported-binary-operation
|
||||
return m.LibraryItem.objects.filter(
|
||||
(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.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 serializers as s
|
||||
from .. import utils
|
||||
|
||||
|
||||
@extend_schema(tags=['Version'])
|
||||
|
@ -32,7 +34,7 @@ class VersionViewset(
|
|||
summary='restore version data into current item',
|
||||
request=None,
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||
c.HTTP_200_OK: RSFormParseSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
|
@ -42,81 +44,10 @@ class VersionViewset(
|
|||
''' Restore version data into current item. '''
|
||||
version = cast(m.Version, self.get_object())
|
||||
item = cast(m.LibraryItem, version.item)
|
||||
schema = m.RSForm.objects.get(pk=item.pk)
|
||||
s.RSFormSerializer(schema).restore_from_version(version.data)
|
||||
RSFormSerializer(item).restore_from_version(version.data)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.RSFormParseSerializer(schema).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
|
||||
data=RSFormParseSerializer(item).data
|
||||
)
|
||||
|
||||
|
||||
|
@ -136,10 +67,79 @@ def export_file(request: Request, pk: int):
|
|||
version = m.Version.objects.get(pk=pk)
|
||||
except m.Version.DoesNotExist:
|
||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||
schema = m.RSForm.objects.get(pk=version.item.pk)
|
||||
data = s.RSFormTRSSerializer(schema).from_versioned_data(version.data)
|
||||
file = utils.write_zipped_json(data, utils.EXTEOR_INNER_FILENAME)
|
||||
data = RSFormTRSSerializer(version.item).from_versioned_data(version.data)
|
||||
file = utility.write_zipped_json(data, utils.EXTEOR_INNER_FILENAME)
|
||||
filename = utils.filename_for_schema(data['alias'])
|
||||
response = HttpResponse(file, content_type='application/zip')
|
||||
response['Content-Disposition'] = f'attachment; filename={filename}'
|
||||
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.Argument, ArgumentAdmin)
|
||||
admin.site.register(models.SynthesisSubstitution, SynthesisSubstitutionAdmin)
|
||||
admin.site.register(models.Substitution, SynthesisSubstitutionAdmin)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RsformConfig(AppConfig):
|
||||
class OssConfig(AppConfig):
|
||||
''' Application config. '''
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
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
|
||||
from django.db import migrations, models
|
||||
|
@ -9,7 +9,8 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('rsform', '0008_alter_libraryitem_item_type'),
|
||||
('library', '0001_initial'),
|
||||
('rsform', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
@ -17,17 +18,15 @@ class Migration(migrations.Migration):
|
|||
name='Operation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('operation_type', models.CharField(choices=[
|
||||
('input', 'Input'), ('synthesis', 'Synthesis')], default='input', max_length=10, verbose_name='Тип')),
|
||||
('operation_type', models.CharField(choices=[('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='Шифр')),
|
||||
('title', models.TextField(blank=True, verbose_name='Название')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Комментарий')),
|
||||
('position_x', models.FloatField(default=0, verbose_name='Положение по горизонтали')),
|
||||
('position_y', models.FloatField(default=0, verbose_name='Положение по вертикали')),
|
||||
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='items', to='rsform.libraryitem', verbose_name='Схема синтеза')),
|
||||
('result', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='producer', to='rsform.libraryitem', verbose_name='Связанная КС')),
|
||||
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='library.libraryitem', verbose_name='Схема синтеза')),
|
||||
('result', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='producer', to='library.libraryitem', verbose_name='Связанная КС')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Операция',
|
||||
|
@ -35,16 +34,13 @@ class Migration(migrations.Migration):
|
|||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SynthesisSubstitution',
|
||||
name='Substitution',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('transfer_term', models.BooleanField(default=False, verbose_name='Перенос термина')),
|
||||
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
||||
to='oss.operation', verbose_name='Операция')),
|
||||
('original', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='as_original', to='rsform.constituenta', verbose_name='Удаляемая конституента')),
|
||||
('substitution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='as_substitute', to='rsform.constituenta', verbose_name='Замещающая конституента')),
|
||||
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oss.operation', verbose_name='Операция')),
|
||||
('original', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='as_original', to='rsform.constituenta', verbose_name='Удаляемая конституента')),
|
||||
('substitution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='as_substitute', to='rsform.constituenta', verbose_name='Замещающая конституента')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Отождествление синтеза',
|
||||
|
@ -55,10 +51,8 @@ class Migration(migrations.Migration):
|
|||
name='Argument',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('argument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='descendants', to='oss.operation', verbose_name='Аргумент')),
|
||||
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='arguments', to='oss.operation', verbose_name='Операция')),
|
||||
('argument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='descendants', to='oss.operation', verbose_name='Аргумент')),
|
||||
('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arguments', to='oss.operation', verbose_name='Операция')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Аргумент',
|
||||
|
|
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 (
|
||||
CASCADE,
|
||||
SET_NULL,
|
||||
BooleanField,
|
||||
CharField,
|
||||
FloatField,
|
||||
ForeignKey,
|
||||
|
@ -21,7 +22,7 @@ class Operation(Model):
|
|||
''' Operational schema Unit.'''
|
||||
oss: ForeignKey = ForeignKey(
|
||||
verbose_name='Схема синтеза',
|
||||
to='oss.OperationSchema',
|
||||
to='library.LibraryItem',
|
||||
on_delete=CASCADE,
|
||||
related_name='items'
|
||||
)
|
||||
|
@ -33,11 +34,15 @@ class Operation(Model):
|
|||
)
|
||||
result: ForeignKey = ForeignKey(
|
||||
verbose_name='Связанная КС',
|
||||
to='rsform.LibraryItem',
|
||||
to='library.LibraryItem',
|
||||
null=True,
|
||||
on_delete=SET_NULL,
|
||||
related_name='producer'
|
||||
)
|
||||
sync_text: BooleanField = BooleanField(
|
||||
verbose_name='Синхронизация',
|
||||
default=True
|
||||
)
|
||||
|
||||
alias: CharField = CharField(
|
||||
verbose_name='Шифр',
|
||||
|
|
|
@ -3,47 +3,53 @@ from typing import Optional
|
|||
|
||||
from django.core.exceptions import ValidationError
|
||||
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 .Argument import Argument
|
||||
from .Operation import Operation
|
||||
from .SynthesisSubstitution import SynthesisSubstitution
|
||||
from .Substitution import Substitution
|
||||
|
||||
|
||||
class OperationSchema(LibraryItem):
|
||||
class OperationSchema:
|
||||
''' Operations schema API. '''
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
proxy = True
|
||||
def __init__(self, model: LibraryItem):
|
||||
self.model = model
|
||||
|
||||
class InternalManager(Manager):
|
||||
''' Object manager. '''
|
||||
@staticmethod
|
||||
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:
|
||||
return super().get_queryset().filter(item_type=LibraryItemType.OPERATION_SCHEMA)
|
||||
@staticmethod
|
||||
def from_id(pk: int) -> 'OperationSchema':
|
||||
''' Get LibraryItem by pk. '''
|
||||
model = LibraryItem.objects.get(pk=pk)
|
||||
return OperationSchema(model)
|
||||
|
||||
def create(self, **kwargs):
|
||||
kwargs.update({'item_type': LibraryItemType.OPERATION_SCHEMA})
|
||||
return super().create(**kwargs)
|
||||
def save(self, *args, **kwargs):
|
||||
''' Save wrapper. '''
|
||||
self.model.save(*args, **kwargs)
|
||||
|
||||
# Legit overriding object manager
|
||||
objects = InternalManager() # type: ignore[misc]
|
||||
def refresh_from_db(self):
|
||||
''' Model wrapper. '''
|
||||
self.model.refresh_from_db()
|
||||
|
||||
def operations(self) -> QuerySet[Operation]:
|
||||
''' 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]:
|
||||
''' 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. '''
|
||||
return SynthesisSubstitution.objects.filter(operation__oss=self)
|
||||
return Substitution.objects.filter(operation__oss=self.model)
|
||||
|
||||
def update_positions(self, data: list[dict]):
|
||||
''' Update positions. '''
|
||||
|
@ -60,7 +66,7 @@ class OperationSchema(LibraryItem):
|
|||
''' Insert new operation. '''
|
||||
if kwargs['alias'] != '' and self.operations().filter(alias=kwargs['alias']).exists():
|
||||
raise ValidationError(msg.aliasTaken(kwargs['alias']))
|
||||
result = Operation.objects.create(oss=self, **kwargs)
|
||||
result = Operation.objects.create(oss=self.model, **kwargs)
|
||||
self.save()
|
||||
result.refresh_from_db()
|
||||
return result
|
||||
|
@ -109,7 +115,7 @@ class OperationSchema(LibraryItem):
|
|||
return
|
||||
|
||||
Argument.objects.filter(operation=target).delete()
|
||||
SynthesisSubstitution.objects.filter(operation=target).delete()
|
||||
Substitution.objects.filter(operation=target).delete()
|
||||
|
||||
# trigger on_change effects
|
||||
|
||||
|
@ -118,9 +124,9 @@ class OperationSchema(LibraryItem):
|
|||
@transaction.atomic
|
||||
def set_substitutions(self, target: Operation, substitutes: list[dict]):
|
||||
''' Clear all arguments for operation. '''
|
||||
SynthesisSubstitution.objects.filter(operation=target).delete()
|
||||
Substitution.objects.filter(operation=target).delete()
|
||||
for sub in substitutes:
|
||||
SynthesisSubstitution.objects.create(
|
||||
Substitution.objects.create(
|
||||
operation=target,
|
||||
original=sub['original'],
|
||||
substitution=sub['substitution'],
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from django.db.models import CASCADE, BooleanField, ForeignKey, Model
|
||||
|
||||
|
||||
class SynthesisSubstitution(Model):
|
||||
class Substitution(Model):
|
||||
''' Substitutions as part of Synthesis operation in OSS.'''
|
||||
operation: ForeignKey = ForeignKey(
|
||||
verbose_name='Операция',
|
|
@ -1,8 +1,7 @@
|
|||
''' Django: Models. '''
|
||||
|
||||
from apps.rsform.models import LibraryItem, LibraryItemType
|
||||
|
||||
from .Argument import Argument
|
||||
from .Inheritance import Inheritance
|
||||
from .Operation import Operation, OperationType
|
||||
from .OperationSchema import OperationSchema
|
||||
from .SynthesisSubstitution import SynthesisSubstitution
|
||||
from .Substitution import Substitution
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
''' REST API: Serializers. '''
|
||||
|
||||
from apps.rsform.serializers import LibraryItemSerializer
|
||||
|
||||
from .basics import OperationPositionSerializer, PositionsSerializer, SubstitutionExSerializer
|
||||
from .data_access import (
|
||||
ArgumentSerializer,
|
||||
|
@ -10,4 +8,4 @@ from .data_access import (
|
|||
OperationSchemaSerializer,
|
||||
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.serializers import PrimaryKeyRelatedField as PKField
|
||||
|
||||
from apps.rsform.models import LibraryItem
|
||||
from apps.rsform.serializers import LibraryItemDetailsSerializer
|
||||
from apps.library.models import LibraryItem
|
||||
from apps.library.serializers import LibraryItemDetailsSerializer
|
||||
from shared import messages as msg
|
||||
|
||||
from ..models import Argument, Operation, OperationSchema, OperationType
|
||||
|
@ -41,9 +41,10 @@ class OperationCreateSerializer(serializers.Serializer):
|
|||
''' serializer metadata. '''
|
||||
model = Operation
|
||||
fields = \
|
||||
'alias', 'operation_type', 'title', \
|
||||
'alias', 'operation_type', 'title', 'sync_text', \
|
||||
'comment', 'result', 'position_x', 'position_y'
|
||||
|
||||
create_schema = serializers.BooleanField(default=False, required=False)
|
||||
item_data = OperationData()
|
||||
arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
|
||||
positions = serializers.ListField(
|
||||
|
@ -85,19 +86,20 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = OperationSchema
|
||||
model = LibraryItem
|
||||
fields = '__all__'
|
||||
|
||||
def to_representation(self, instance: OperationSchema):
|
||||
def to_representation(self, instance: LibraryItem):
|
||||
result = LibraryItemDetailsSerializer(instance).data
|
||||
oss = OperationSchema(instance)
|
||||
result['items'] = []
|
||||
for operation in instance.operations():
|
||||
for operation in oss.operations():
|
||||
result['items'].append(OperationSerializer(operation).data)
|
||||
result['arguments'] = []
|
||||
for argument in instance.arguments():
|
||||
for argument in oss.arguments():
|
||||
result['arguments'].append(ArgumentSerializer(argument).data)
|
||||
result['substitutions'] = []
|
||||
for substitution in instance.substitutions().values(
|
||||
for substitution in oss.substitutions().values(
|
||||
'operation',
|
||||
'original',
|
||||
'substitution',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
''' Tests for Django Models. '''
|
||||
from .t_Argument import *
|
||||
from .t_Operation import *
|
||||
from .t_SynthesisSubstitution import *
|
||||
from .t_Substitution import *
|
||||
|
|
|
@ -8,11 +8,23 @@ class TestArgument(TestCase):
|
|||
''' Testing Argument model. '''
|
||||
|
||||
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.operation2 = Operation.objects.create(oss=self.oss, alias='KS2', operation_type=OperationType.SYNTHESIS)
|
||||
self.operation3 = Operation.objects.create(oss=self.oss, alias='KS3', operation_type=OperationType.INPUT)
|
||||
self.operation1 = Operation.objects.create(
|
||||
oss=self.oss.model,
|
||||
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(
|
||||
operation=self.operation2,
|
||||
argument=self.operation1
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
''' Testing models: Operation. '''
|
||||
from django.test import TestCase
|
||||
|
||||
from apps.library.models import LibraryItem, LibraryItemType
|
||||
from apps.oss.models import Operation, OperationSchema, OperationType
|
||||
from apps.rsform.models import RSForm
|
||||
|
||||
|
||||
class TestOperation(TestCase):
|
||||
''' Testing Operation model. '''
|
||||
|
||||
def setUp(self):
|
||||
self.oss = OperationSchema.objects.create(alias='T1')
|
||||
self.oss = OperationSchema.create(alias='T1')
|
||||
self.operation = Operation.objects.create(
|
||||
oss=self.oss,
|
||||
oss=self.oss.model,
|
||||
alias='KS1'
|
||||
)
|
||||
|
||||
|
@ -21,11 +23,54 @@ class TestOperation(TestCase):
|
|||
|
||||
|
||||
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.result, None)
|
||||
self.assertEqual(self.operation.alias, 'KS1')
|
||||
self.assertEqual(self.operation.title, '')
|
||||
self.assertEqual(self.operation.comment, '')
|
||||
self.assertEqual(self.operation.sync_text, True)
|
||||
self.assertEqual(self.operation.position_x, 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)
|
||||
|
|
|
@ -3,13 +3,7 @@ from unittest import result
|
|||
|
||||
from django.test import TestCase
|
||||
|
||||
from apps.oss.models import (
|
||||
Argument,
|
||||
Operation,
|
||||
OperationSchema,
|
||||
OperationType,
|
||||
SynthesisSubstitution
|
||||
)
|
||||
from apps.oss.models import Argument, Operation, OperationSchema, OperationType, Substitution
|
||||
from apps.rsform.models import RSForm
|
||||
|
||||
|
||||
|
@ -17,24 +11,30 @@ class TestSynthesisSubstitution(TestCase):
|
|||
''' Testing Synthesis Substitution model. '''
|
||||
|
||||
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.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.operation1 = Operation.objects.create(
|
||||
oss=self.oss,
|
||||
oss=self.oss.model,
|
||||
alias='KS1',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks1)
|
||||
result=self.ks1.model
|
||||
)
|
||||
self.operation2 = Operation.objects.create(
|
||||
oss=self.oss,
|
||||
oss=self.oss.model,
|
||||
alias='KS2',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks1)
|
||||
self.operation3 = Operation.objects.create(oss=self.oss, alias='KS3', operation_type=OperationType.SYNTHESIS)
|
||||
result=self.ks1.model
|
||||
)
|
||||
self.operation3 = Operation.objects.create(
|
||||
oss=self.oss.model,
|
||||
alias='KS3',
|
||||
operation_type=OperationType.SYNTHESIS
|
||||
)
|
||||
Argument.objects.create(
|
||||
operation=self.operation3,
|
||||
argument=self.operation1
|
||||
|
@ -44,7 +44,7 @@ class TestSynthesisSubstitution(TestCase):
|
|||
argument=self.operation2
|
||||
)
|
||||
|
||||
self.substitution = SynthesisSubstitution.objects.create(
|
||||
self.substitution = Substitution.objects.create(
|
||||
operation=self.operation3,
|
||||
original=self.ks1x1,
|
||||
substitution=self.ks2x1,
|
||||
|
@ -58,18 +58,18 @@ class TestSynthesisSubstitution(TestCase):
|
|||
|
||||
|
||||
def test_cascade_delete_operation(self):
|
||||
self.assertEqual(SynthesisSubstitution.objects.count(), 1)
|
||||
self.assertEqual(Substitution.objects.count(), 1)
|
||||
self.operation3.delete()
|
||||
self.assertEqual(SynthesisSubstitution.objects.count(), 0)
|
||||
self.assertEqual(Substitution.objects.count(), 0)
|
||||
|
||||
|
||||
def test_cascade_delete_original(self):
|
||||
self.assertEqual(SynthesisSubstitution.objects.count(), 1)
|
||||
self.assertEqual(Substitution.objects.count(), 1)
|
||||
self.ks1x1.delete()
|
||||
self.assertEqual(SynthesisSubstitution.objects.count(), 0)
|
||||
self.assertEqual(Substitution.objects.count(), 0)
|
||||
|
||||
|
||||
def test_cascade_delete_substitution(self):
|
||||
self.assertEqual(SynthesisSubstitution.objects.count(), 1)
|
||||
self.assertEqual(Substitution.objects.count(), 1)
|
||||
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 apps.library.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead
|
||||
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
|
||||
|
||||
|
||||
|
@ -12,29 +13,29 @@ class TestOssViewset(EndpointTester):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.owned = OperationSchema.objects.create(title='Test', alias='T1', owner=self.user)
|
||||
self.owned_id = self.owned.pk
|
||||
self.unowned = OperationSchema.objects.create(title='Test2', alias='T2')
|
||||
self.unowned_id = self.unowned.pk
|
||||
self.private = OperationSchema.objects.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
||||
self.private_id = self.private.pk
|
||||
self.invalid_id = self.private.pk + 1337
|
||||
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
|
||||
self.owned_id = self.owned.model.pk
|
||||
self.unowned = OperationSchema.create(title='Test2', alias='T2')
|
||||
self.unowned_id = self.unowned.model.pk
|
||||
self.private = OperationSchema.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
||||
self.private_id = self.private.model.pk
|
||||
self.invalid_id = self.private.model.pk + 1337
|
||||
|
||||
|
||||
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.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.operation1 = self.owned.create_operation(
|
||||
alias='1',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks1
|
||||
result=self.ks1.model
|
||||
)
|
||||
self.operation2 = self.owned.create_operation(
|
||||
alias='2',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks2
|
||||
result=self.ks2.model
|
||||
)
|
||||
self.operation3 = self.owned.create_operation(
|
||||
alias='3',
|
||||
|
@ -53,12 +54,12 @@ class TestOssViewset(EndpointTester):
|
|||
self.populateData()
|
||||
|
||||
response = self.executeOK(item=self.owned_id)
|
||||
self.assertEqual(response.data['owner'], self.owned.owner.pk)
|
||||
self.assertEqual(response.data['title'], self.owned.title)
|
||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
||||
self.assertEqual(response.data['location'], self.owned.location)
|
||||
self.assertEqual(response.data['access_policy'], self.owned.access_policy)
|
||||
self.assertEqual(response.data['visible'], self.owned.visible)
|
||||
self.assertEqual(response.data['owner'], self.owned.model.owner.pk)
|
||||
self.assertEqual(response.data['title'], self.owned.model.title)
|
||||
self.assertEqual(response.data['alias'], self.owned.model.alias)
|
||||
self.assertEqual(response.data['location'], self.owned.model.location)
|
||||
self.assertEqual(response.data['access_policy'], self.owned.model.access_policy)
|
||||
self.assertEqual(response.data['visible'], self.owned.model.visible)
|
||||
|
||||
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.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')
|
||||
def test_create_operation(self):
|
||||
self.executeNotFound(item=self.invalid_id)
|
||||
|
||||
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
@ -136,6 +137,7 @@ class TestOssViewset(EndpointTester):
|
|||
'alias': 'Test3',
|
||||
'title': 'Test title',
|
||||
'comment': 'Тест кириллицы',
|
||||
'sync_text': False,
|
||||
'position_x': 1,
|
||||
'position_y': 1,
|
||||
},
|
||||
|
@ -149,7 +151,9 @@ class TestOssViewset(EndpointTester):
|
|||
self.executeBadData(data=data)
|
||||
|
||||
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)
|
||||
new_operation = response.data['new_operation']
|
||||
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['position_x'], data['item_data']['position_x'])
|
||||
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.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
|
||||
|
@ -193,15 +198,55 @@ class TestOssViewset(EndpointTester):
|
|||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'operation_type': OperationType.INPUT,
|
||||
'result': self.ks1.pk
|
||||
'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.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')
|
||||
def test_delete_operation(self):
|
||||
|
|
|
@ -4,9 +4,9 @@ from rest_framework import routers
|
|||
|
||||
from . import views
|
||||
|
||||
library_router = routers.SimpleRouter(trailing_slash=False)
|
||||
library_router.register('oss', views.OssViewSet, 'OSS')
|
||||
oss_router = routers.SimpleRouter(trailing_slash=False)
|
||||
oss_router.register('oss', views.OssViewSet, 'OSS')
|
||||
|
||||
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.response import Response
|
||||
|
||||
from apps.library.models import LibraryItem, LibraryItemType
|
||||
from apps.library.serializers import LibraryItemSerializer
|
||||
from shared import permissions
|
||||
|
||||
from .. import models as m
|
||||
|
@ -20,11 +22,11 @@ from .. import serializers as s
|
|||
@extend_schema_view()
|
||||
class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
||||
''' Endpoint: OperationSchema. '''
|
||||
queryset = m.OperationSchema.objects.all()
|
||||
serializer_class = s.LibraryItemSerializer
|
||||
queryset = LibraryItem.objects.filter(item_type=LibraryItemType.OPERATION_SCHEMA)
|
||||
serializer_class = LibraryItemSerializer
|
||||
|
||||
def _get_schema(self) -> m.OperationSchema:
|
||||
return cast(m.OperationSchema, self.get_object())
|
||||
def _get_item(self) -> LibraryItem:
|
||||
return cast(LibraryItem, self.get_object())
|
||||
|
||||
def get_permissions(self):
|
||||
''' Determine permission class. '''
|
||||
|
@ -52,7 +54,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['get'], url_path='details')
|
||||
def details(self, request: Request, pk):
|
||||
''' Endpoint: Detailed OSS data. '''
|
||||
serializer = s.OperationSchemaSerializer(self._get_schema())
|
||||
serializer = s.OperationSchemaSerializer(self._get_item())
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=serializer.data
|
||||
|
@ -71,10 +73,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='update-positions')
|
||||
def update_positions(self, request: Request, pk):
|
||||
''' Endpoint: Update operations positions. '''
|
||||
schema = self._get_schema()
|
||||
serializer = s.PositionsSerializer(data=request.data)
|
||||
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)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -91,23 +92,36 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['post'], url_path='create-operation')
|
||||
def create_operation(self, request: Request, pk):
|
||||
''' Create new operation. '''
|
||||
schema = self._get_schema()
|
||||
serializer = s.OperationCreateSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
with transaction.atomic():
|
||||
schema.update_positions(serializer.validated_data['positions'])
|
||||
new_operation = schema.create_operation(**serializer.validated_data['item_data'])
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
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:
|
||||
for argument in serializer.validated_data['arguments']:
|
||||
schema.add_argument(operation=new_operation, argument=argument)
|
||||
schema.refresh_from_db()
|
||||
oss.add_argument(operation=new_operation, argument=argument)
|
||||
|
||||
oss.refresh_from_db()
|
||||
response = Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_operation': s.OperationSerializer(new_operation).data,
|
||||
'oss': s.OperationSchemaSerializer(schema).data
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
}
|
||||
)
|
||||
return response
|
||||
|
@ -126,19 +140,19 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
||||
def delete_operation(self, request: Request, pk):
|
||||
''' Endpoint: Delete operation. '''
|
||||
schema = self._get_schema()
|
||||
serializer = s.OperationDeleteSerializer(
|
||||
data=request.data,
|
||||
context={'oss': schema}
|
||||
context={'oss': self.get_object()}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
with transaction.atomic():
|
||||
schema.update_positions(serializer.validated_data['positions'])
|
||||
schema.delete_operation(serializer.validated_data['target'])
|
||||
schema.refresh_from_db()
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
oss.delete_operation(serializer.validated_data['target'])
|
||||
|
||||
oss.refresh_from_db()
|
||||
return Response(
|
||||
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']
|
||||
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.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
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -12,29 +10,10 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('library', '0001_initial'),
|
||||
]
|
||||
|
||||
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(
|
||||
name='Constituenta',
|
||||
fields=[
|
||||
|
@ -45,28 +24,15 @@ class Migration(migrations.Migration):
|
|||
('convention', 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_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_raw', 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='Концептуальная схема')),
|
||||
('definition_raw', 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='library.libraryitem', verbose_name='Концептуальная схема')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Конституента',
|
||||
'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
|
||||
|
||||
|
||||
def _empty_forms():
|
||||
return []
|
||||
|
||||
|
||||
class CstType(TextChoices):
|
||||
''' Type of constituenta. '''
|
||||
BASE = 'basic'
|
||||
|
@ -40,7 +36,7 @@ class Constituenta(Model):
|
|||
''' Constituenta is the base unit for every conceptual schema. '''
|
||||
schema: ForeignKey = ForeignKey(
|
||||
verbose_name='Концептуальная схема',
|
||||
to='rsform.RSForm',
|
||||
to='library.LibraryItem',
|
||||
on_delete=CASCADE
|
||||
)
|
||||
order: PositiveIntegerField = PositiveIntegerField(
|
||||
|
@ -76,7 +72,7 @@ class Constituenta(Model):
|
|||
)
|
||||
term_forms: JSONField = JSONField(
|
||||
verbose_name='Словоформы',
|
||||
default=_empty_forms
|
||||
default=list
|
||||
)
|
||||
definition_formal: TextField = TextField(
|
||||
verbose_name='Родоструктурное определение',
|
||||
|
|
|
@ -5,8 +5,9 @@ from typing import Optional, cast
|
|||
from cctext import Entity, Resolver, TermForm, extract_entities, split_grams
|
||||
from django.core.exceptions import ValidationError
|
||||
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 ..graph import Graph
|
||||
|
@ -22,35 +23,39 @@ from .api_RSLanguage import (
|
|||
split_template
|
||||
)
|
||||
from .Constituenta import Constituenta, CstType
|
||||
from .LibraryItem import LibraryItem, LibraryItemType
|
||||
from .Version import Version
|
||||
|
||||
_INSERT_LAST: int = -1
|
||||
|
||||
|
||||
class RSForm(LibraryItem):
|
||||
class RSForm:
|
||||
''' RSForm is math form of conceptual schema. '''
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
proxy = True
|
||||
def __init__(self, model: LibraryItem):
|
||||
self.model = model
|
||||
|
||||
class InternalManager(Manager):
|
||||
''' Object manager. '''
|
||||
@staticmethod
|
||||
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:
|
||||
return super().get_queryset().filter(item_type=LibraryItemType.RSFORM)
|
||||
@staticmethod
|
||||
def from_id(pk: int) -> 'RSForm':
|
||||
''' Get LibraryItem by pk. '''
|
||||
model = LibraryItem.objects.get(pk=pk)
|
||||
return RSForm(model)
|
||||
|
||||
def create(self, **kwargs):
|
||||
kwargs.update({'item_type': LibraryItemType.RSFORM})
|
||||
return super().create(**kwargs)
|
||||
def save(self, *args, **kwargs):
|
||||
''' Model wrapper. '''
|
||||
self.model.save(*args, **kwargs)
|
||||
|
||||
# Legit overriding object manager
|
||||
objects = InternalManager() # type: ignore[misc]
|
||||
def refresh_from_db(self):
|
||||
''' Model wrapper. '''
|
||||
self.model.refresh_from_db()
|
||||
|
||||
def constituents(self) -> QuerySet[Constituenta]:
|
||||
''' 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:
|
||||
''' Create resolver for text references based on schema terms. '''
|
||||
|
@ -106,7 +111,7 @@ class RSForm(LibraryItem):
|
|||
''' Get maximum alias index for specific CstType. '''
|
||||
result: int = 0
|
||||
items = Constituenta.objects \
|
||||
.filter(schema=self, cst_type=cst_type) \
|
||||
.filter(schema=self.model, cst_type=cst_type) \
|
||||
.order_by('-alias') \
|
||||
.values_list('alias', flat=True)
|
||||
for alias in items:
|
||||
|
@ -158,7 +163,7 @@ class RSForm(LibraryItem):
|
|||
cst_type = guess_type(alias)
|
||||
self._shift_positions(position, 1)
|
||||
result = Constituenta.objects.create(
|
||||
schema=self,
|
||||
schema=self.model,
|
||||
order=position,
|
||||
alias=alias,
|
||||
cst_type=cst_type,
|
||||
|
@ -191,7 +196,7 @@ class RSForm(LibraryItem):
|
|||
result = deepcopy(items)
|
||||
for cst in result:
|
||||
cst.pk = None
|
||||
cst.schema = self
|
||||
cst.schema = self.model
|
||||
cst.order = position
|
||||
cst.alias = mapping[cst.alias]
|
||||
cst.apply_mapping(mapping)
|
||||
|
@ -304,7 +309,7 @@ class RSForm(LibraryItem):
|
|||
def create_version(self, version: str, description: str, data) -> Version:
|
||||
''' Creates version for current state. '''
|
||||
return Version.objects.create(
|
||||
item=self,
|
||||
item=self.model,
|
||||
version=version,
|
||||
description=description,
|
||||
data=data
|
||||
|
@ -330,7 +335,7 @@ class RSForm(LibraryItem):
|
|||
prefix = get_type_prefix(cst_type)
|
||||
for text in expressions:
|
||||
new_item = Constituenta.objects.create(
|
||||
schema=self,
|
||||
schema=self.model,
|
||||
order=position,
|
||||
alias=f'{prefix}{free_index}',
|
||||
definition_formal=text,
|
||||
|
@ -349,7 +354,7 @@ class RSForm(LibraryItem):
|
|||
update_list = \
|
||||
Constituenta.objects \
|
||||
.only('id', 'order', 'schema') \
|
||||
.filter(schema=self.pk, order__gte=start)
|
||||
.filter(schema=self.model, order__gte=start)
|
||||
for cst in update_list:
|
||||
cst.order += shift
|
||||
Constituenta.objects.bulk_update(update_list, ['order'])
|
||||
|
|
|
@ -1,16 +1,4 @@
|
|||
''' Django: Models. '''
|
||||
|
||||
from .Constituenta import Constituenta, CstType
|
||||
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. '''
|
||||
|
||||
from .basics import (
|
||||
AccessPolicySerializer,
|
||||
ASTNodeSerializer,
|
||||
ExpressionParseSerializer,
|
||||
ExpressionSerializer,
|
||||
LocationSerializer,
|
||||
MultiFormSerializer,
|
||||
ResolverSerializer,
|
||||
TextSerializer,
|
||||
|
@ -20,22 +18,9 @@ from .data_access import (
|
|||
CstSubstituteSerializer,
|
||||
CstTargetSerializer,
|
||||
InlineSynthesisSerializer,
|
||||
LibraryItemBaseSerializer,
|
||||
LibraryItemCloneSerializer,
|
||||
LibraryItemDetailsSerializer,
|
||||
LibraryItemSerializer,
|
||||
RSFormParseSerializer,
|
||||
RSFormSerializer,
|
||||
UsersListSerializer,
|
||||
UserTargetSerializer,
|
||||
VersionCreateSerializer,
|
||||
VersionSerializer
|
||||
RSFormSerializer
|
||||
)
|
||||
from .io_files import FileSerializer, RSFormTRSSerializer, RSFormUploadSerializer
|
||||
from .io_pyconcept import PyConceptAdapter
|
||||
from .schema_typing import (
|
||||
NewCstResponse,
|
||||
NewMultiCstResponse,
|
||||
NewVersionResponse,
|
||||
ResultTextResponse
|
||||
)
|
||||
from .responses import NewCstResponse, NewMultiCstResponse, ResultTextResponse
|
||||
|
|
|
@ -4,10 +4,6 @@ from typing import cast
|
|||
from cctext import EntityReference, Reference, ReferenceType, Resolver, SyntacticReference
|
||||
from rest_framework import serializers
|
||||
|
||||
from shared import messages as msg
|
||||
|
||||
from ..models import AccessPolicy, validate_location
|
||||
|
||||
|
||||
class ExpressionSerializer(serializers.Serializer):
|
||||
''' Serializer: RSLang expression. '''
|
||||
|
@ -20,32 +16,6 @@ class WordFormSerializer(serializers.Serializer):
|
|||
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):
|
||||
''' Serializer: inflect request. '''
|
||||
items = serializers.ListField(
|
||||
|
|
|
@ -7,89 +7,15 @@ from django.db import transaction
|
|||
from rest_framework import serializers
|
||||
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 ..models import Constituenta, CstType, LibraryItem, RSForm, Version
|
||||
from ..models import Constituenta, CstType, RSForm
|
||||
from .basics import CstParseSerializer
|
||||
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):
|
||||
''' Serializer: Constituenta all data. '''
|
||||
class Meta:
|
||||
|
@ -112,18 +38,19 @@ class CstSerializer(serializers.ModelSerializer):
|
|||
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_changed = 'term_forms' in data
|
||||
schema = RSForm(instance.schema)
|
||||
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:
|
||||
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:
|
||||
data['term_forms'] = []
|
||||
term_changed = data['term_resolved'] != instance.term_resolved
|
||||
result: Constituenta = super().update(instance, data)
|
||||
if term_changed:
|
||||
instance.schema.on_term_change([result.id])
|
||||
schema.on_term_change([result.id])
|
||||
result.refresh_from_db()
|
||||
instance.schema.save()
|
||||
schema.save()
|
||||
return result
|
||||
|
||||
|
||||
|
@ -169,16 +96,16 @@ class RSFormSerializer(serializers.ModelSerializer):
|
|||
model = LibraryItem
|
||||
fields = '__all__'
|
||||
|
||||
def to_representation(self, instance: RSForm) -> dict:
|
||||
def to_representation(self, instance: LibraryItem) -> dict:
|
||||
result = LibraryItemDetailsSerializer(instance).data
|
||||
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)
|
||||
return result
|
||||
|
||||
def to_versioned_data(self) -> dict:
|
||||
''' 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['subscribers']
|
||||
del result['editors']
|
||||
|
@ -195,14 +122,14 @@ class RSFormSerializer(serializers.ModelSerializer):
|
|||
|
||||
def from_versioned_data(self, version: int, data: dict) -> dict:
|
||||
''' Load data from version. '''
|
||||
result = self.to_representation(cast(RSForm, self.instance))
|
||||
result = self.to_representation(cast(LibraryItem, self.instance))
|
||||
result['version'] = version
|
||||
return result | data
|
||||
|
||||
@transaction.atomic
|
||||
def restore_from_version(self, data: dict):
|
||||
''' Load data from version. '''
|
||||
schema = cast(RSForm, self.instance)
|
||||
schema = RSForm(cast(LibraryItem, self.instance))
|
||||
items: list[dict] = data['items']
|
||||
ids: list[int] = [item['id'] for item in items]
|
||||
processed: list[int] = []
|
||||
|
@ -256,13 +183,13 @@ class RSFormParseSerializer(serializers.ModelSerializer):
|
|||
model = LibraryItem
|
||||
fields = '__all__'
|
||||
|
||||
def to_representation(self, instance: RSForm):
|
||||
def to_representation(self, instance: LibraryItem):
|
||||
result = RSFormSerializer(instance).data
|
||||
return self._parse_data(result)
|
||||
|
||||
def from_versioned_data(self, version: int, data: dict) -> dict:
|
||||
''' Load data from version and parse. '''
|
||||
item = cast(RSForm, self.instance)
|
||||
item = cast(LibraryItem, self.instance)
|
||||
result = RSFormSerializer(item).from_versioned_data(version, data)
|
||||
return self._parse_data(result)
|
||||
|
||||
|
@ -281,7 +208,7 @@ class CstTargetSerializer(serializers.Serializer):
|
|||
target = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = cast(RSForm, self.context['schema'])
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
cst = cast(Constituenta, attrs['target'])
|
||||
if schema and cst.schema != schema:
|
||||
raise serializers.ValidationError({
|
||||
|
@ -295,16 +222,6 @@ class CstTargetSerializer(serializers.Serializer):
|
|||
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):
|
||||
''' Serializer: Constituenta renaming. '''
|
||||
target = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
|
@ -313,7 +230,7 @@ class CstRenameSerializer(serializers.Serializer):
|
|||
|
||||
def validate(self, attrs):
|
||||
attrs = super().validate(attrs)
|
||||
schema = cast(RSForm, self.context['schema'])
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
cst = cast(Constituenta, attrs['target'])
|
||||
if cst.schema != schema:
|
||||
raise serializers.ValidationError({
|
||||
|
@ -324,7 +241,7 @@ class CstRenameSerializer(serializers.Serializer):
|
|||
raise serializers.ValidationError({
|
||||
'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({
|
||||
'alias': msg.aliasTaken(new_alias)
|
||||
})
|
||||
|
@ -336,7 +253,7 @@ class CstListSerializer(serializers.Serializer):
|
|||
items = PKField(many=True, queryset=Constituenta.objects.all())
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = cast(RSForm, self.context['schema'])
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
if not schema:
|
||||
return attrs
|
||||
|
||||
|
@ -368,7 +285,7 @@ class CstSubstituteSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = cast(RSForm, self.context['schema'])
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
deleted = set()
|
||||
for item in attrs['substitutions']:
|
||||
original_cst = cast(Constituenta, item['original'])
|
||||
|
@ -395,8 +312,8 @@ class CstSubstituteSerializer(serializers.Serializer):
|
|||
|
||||
class InlineSynthesisSerializer(serializers.Serializer):
|
||||
''' Serializer: Inline synthesis operation input. '''
|
||||
receiver = PKField(many=False, queryset=RSForm.objects.all())
|
||||
source = PKField(many=False, queryset=RSForm.objects.all()) # type: ignore
|
||||
receiver = PKField(many=False, queryset=LibraryItem.objects.all())
|
||||
source = PKField(many=False, queryset=LibraryItem.objects.all()) # type: ignore
|
||||
items = PKField(many=True, queryset=Constituenta.objects.all())
|
||||
substitutions = serializers.ListField(
|
||||
child=CstSubstituteSerializerBase()
|
||||
|
@ -404,8 +321,8 @@ class InlineSynthesisSerializer(serializers.Serializer):
|
|||
|
||||
def validate(self, attrs):
|
||||
user = cast(User, self.context['user'])
|
||||
schema_in = cast(RSForm, attrs['source'])
|
||||
schema_out = cast(RSForm, attrs['receiver'])
|
||||
schema_in = cast(LibraryItem, attrs['source'])
|
||||
schema_out = cast(LibraryItem, attrs['receiver'])
|
||||
if user.is_anonymous or (schema_out.owner != user and not user.is_staff):
|
||||
raise PermissionDenied({
|
||||
'message': msg.schemaNotOwned(),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.library.models import LibraryItem
|
||||
from shared import messages as msg
|
||||
|
||||
from ..models import Constituenta, RSForm
|
||||
|
@ -29,14 +30,14 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
''' Serializer: TRS file production and loading for RSForm. '''
|
||||
|
||||
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')
|
||||
for cst in items:
|
||||
result['items'].append(self._prepare_json_constituenta(cst))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _prepare_json_rsform(schema: RSForm) -> dict:
|
||||
def _prepare_json_rsform(schema: LibraryItem) -> dict:
|
||||
return {
|
||||
'type': _TRS_TYPE,
|
||||
'title': schema.title,
|
||||
|
@ -125,7 +126,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
result['comment'] = data.get('comment', '')
|
||||
if 'id' in data:
|
||||
result['id'] = data['id']
|
||||
self.instance = RSForm.objects.get(pk=result['id'])
|
||||
self.instance = RSForm.from_id(result['id'])
|
||||
return result
|
||||
|
||||
def validate(self, attrs: dict):
|
||||
|
@ -139,7 +140,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data: dict) -> RSForm:
|
||||
self.instance: RSForm = RSForm.objects.create(
|
||||
self.instance: RSForm = RSForm.create(
|
||||
owner=validated_data.get('owner', None),
|
||||
alias=validated_data['alias'],
|
||||
title=validated_data['title'],
|
||||
|
@ -154,7 +155,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
for cst_data in validated_data['items']:
|
||||
cst = Constituenta(
|
||||
alias=cst_data['alias'],
|
||||
schema=self.instance,
|
||||
schema=self.instance.model,
|
||||
order=order,
|
||||
cst_type=cst_data['cstType'],
|
||||
)
|
||||
|
@ -167,11 +168,11 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
@transaction.atomic
|
||||
def update(self, instance: RSForm, validated_data) -> RSForm:
|
||||
if 'alias' in validated_data:
|
||||
instance.alias = validated_data['alias']
|
||||
instance.model.alias = validated_data['alias']
|
||||
if 'title' in validated_data:
|
||||
instance.title = validated_data['title']
|
||||
instance.model.title = validated_data['title']
|
||||
if 'comment' in validated_data:
|
||||
instance.comment = validated_data['comment']
|
||||
instance.model.comment = validated_data['comment']
|
||||
|
||||
order = 1
|
||||
prev_constituents = instance.constituents()
|
||||
|
@ -188,7 +189,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
else:
|
||||
cst = Constituenta(
|
||||
alias=cst_data['alias'],
|
||||
schema=instance,
|
||||
schema=instance.model,
|
||||
order=order,
|
||||
cst_type=cst_data['cstType'],
|
||||
)
|
||||
|
|
|
@ -21,9 +21,3 @@ class NewMultiCstResponse(serializers.Serializer):
|
|||
child=serializers.IntegerField()
|
||||
)
|
||||
schema = RSFormParseSerializer()
|
||||
|
||||
|
||||
class NewVersionResponse(serializers.Serializer):
|
||||
''' Serializer: Create cst response. '''
|
||||
version = serializers.IntegerField()
|
||||
schema = RSFormParseSerializer()
|
|
@ -1,6 +1,3 @@
|
|||
''' Tests for Django Models. '''
|
||||
from .t_Constituenta import *
|
||||
from .t_Editor import *
|
||||
from .t_LibraryItem 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.test import TestCase
|
||||
|
||||
from apps.rsform.models import Constituenta, CstType, LibraryItemType, RSForm
|
||||
from apps.rsform.models import Constituenta, CstType, RSForm
|
||||
|
||||
|
||||
class TestConstituenta(TestCase):
|
||||
''' Testing Constituenta model. '''
|
||||
|
||||
def setUp(self):
|
||||
self.schema1 = RSForm.objects.create(title='Test1')
|
||||
self.schema2 = RSForm.objects.create(title='Test2')
|
||||
self.schema1 = RSForm.create(title='Test1')
|
||||
self.schema2 = RSForm.create(title='Test2')
|
||||
|
||||
|
||||
def test_str(self):
|
||||
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)
|
||||
|
||||
|
||||
def test_url(self):
|
||||
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}')
|
||||
|
||||
|
||||
def test_order_not_null(self):
|
||||
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):
|
||||
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):
|
||||
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()
|
||||
|
||||
|
||||
|
@ -50,10 +50,10 @@ class TestConstituenta(TestCase):
|
|||
def test_create_default(self):
|
||||
cst = Constituenta.objects.create(
|
||||
alias='X1',
|
||||
schema=self.schema1,
|
||||
schema=self.schema1.model,
|
||||
order=1
|
||||
)
|
||||
self.assertEqual(cst.schema, self.schema1)
|
||||
self.assertEqual(cst.schema, self.schema1.model)
|
||||
self.assertEqual(cst.order, 1)
|
||||
self.assertEqual(cst.alias, 'X1')
|
||||
self.assertEqual(cst.cst_type, CstType.BASE)
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
from django.forms import ValidationError
|
||||
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):
|
||||
|
@ -11,49 +12,49 @@ class TestRSForm(TestCase):
|
|||
def setUp(self):
|
||||
self.user1 = User.objects.create(username='User1')
|
||||
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)
|
||||
|
||||
|
||||
def test_constituents(self):
|
||||
schema1 = RSForm.objects.create(title='Test1')
|
||||
schema2 = RSForm.objects.create(title='Test2')
|
||||
schema1 = RSForm.create(title='Test1')
|
||||
schema2 = RSForm.create(title='Test2')
|
||||
self.assertFalse(schema1.constituents().exists())
|
||||
self.assertFalse(schema2.constituents().exists())
|
||||
|
||||
Constituenta.objects.create(alias='X1', schema=schema1, order=1)
|
||||
Constituenta.objects.create(alias='X2', schema=schema1, order=2)
|
||||
Constituenta.objects.create(alias='X1', schema=schema1.model, order=1)
|
||||
Constituenta.objects.create(alias='X2', schema=schema1.model, order=2)
|
||||
self.assertTrue(schema1.constituents().exists())
|
||||
self.assertFalse(schema2.constituents().exists())
|
||||
self.assertEqual(schema1.constituents().count(), 2)
|
||||
|
||||
|
||||
def test_get_max_index(self):
|
||||
schema1 = RSForm.objects.create(title='Test1')
|
||||
Constituenta.objects.create(alias='X1', schema=schema1, order=1)
|
||||
Constituenta.objects.create(alias='D2', cst_type=CstType.TERM, schema=schema1, order=2)
|
||||
schema1 = RSForm.create(title='Test1')
|
||||
Constituenta.objects.create(alias='X1', schema=schema1.model, order=1)
|
||||
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.TERM), 2)
|
||||
self.assertEqual(schema1.get_max_index(CstType.AXIOM), 0)
|
||||
|
||||
|
||||
def test_insert_at(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
x1 = schema.insert_new('X1')
|
||||
self.assertEqual(x1.order, 1)
|
||||
self.assertEqual(x1.schema, schema)
|
||||
self.assertEqual(x1.schema, schema.model)
|
||||
|
||||
x2 = schema.insert_new('X2', position=1)
|
||||
x1.refresh_from_db()
|
||||
self.assertEqual(x2.order, 1)
|
||||
self.assertEqual(x2.schema, schema)
|
||||
self.assertEqual(x2.schema, schema.model)
|
||||
self.assertEqual(x1.order, 2)
|
||||
|
||||
x3 = schema.insert_new('X3', position=4)
|
||||
x2.refresh_from_db()
|
||||
x1.refresh_from_db()
|
||||
self.assertEqual(x3.order, 3)
|
||||
self.assertEqual(x3.schema, schema)
|
||||
self.assertEqual(x3.schema, schema.model)
|
||||
self.assertEqual(x2.order, 1)
|
||||
self.assertEqual(x1.order, 2)
|
||||
|
||||
|
@ -62,7 +63,7 @@ class TestRSForm(TestCase):
|
|||
x2.refresh_from_db()
|
||||
x1.refresh_from_db()
|
||||
self.assertEqual(x4.order, 3)
|
||||
self.assertEqual(x4.schema, schema)
|
||||
self.assertEqual(x4.schema, schema.model)
|
||||
self.assertEqual(x3.order, 4)
|
||||
self.assertEqual(x2.order, 1)
|
||||
self.assertEqual(x1.order, 2)
|
||||
|
@ -94,11 +95,11 @@ class TestRSForm(TestCase):
|
|||
def test_insert_last(self):
|
||||
x1 = self.schema.insert_new('X1')
|
||||
self.assertEqual(x1.order, 1)
|
||||
self.assertEqual(x1.schema, self.schema)
|
||||
self.assertEqual(x1.schema, self.schema.model)
|
||||
|
||||
x2 = self.schema.insert_new('X2')
|
||||
self.assertEqual(x2.order, 2)
|
||||
self.assertEqual(x2.schema, self.schema)
|
||||
self.assertEqual(x2.schema, self.schema.model)
|
||||
self.assertEqual(x1.order, 1)
|
||||
|
||||
def test_create_cst(self):
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
''' Tests for REST API. '''
|
||||
from .t_library import *
|
||||
from .t_cctext import *
|
||||
from .t_constituents import *
|
||||
from .t_operations import *
|
||||
from .t_rsforms import *
|
||||
from .t_versions import *
|
||||
|
||||
from .t_cctext import *
|
||||
from .t_rslang import *
|
||||
|
|
|
@ -8,12 +8,12 @@ class TestConstituentaAPI(EndpointTester):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.rsform_owned = RSForm.objects.create(title='Test', alias='T1', owner=self.user)
|
||||
self.rsform_unowned = RSForm.objects.create(title='Test2', alias='T2')
|
||||
self.rsform_owned = RSForm.create(title='Test', alias='T1', owner=self.user)
|
||||
self.rsform_unowned = RSForm.create(title='Test2', alias='T2')
|
||||
self.cst1 = Constituenta.objects.create(
|
||||
alias='X1',
|
||||
cst_type=CstType.BASE,
|
||||
schema=self.rsform_owned,
|
||||
schema=self.rsform_owned.model,
|
||||
order=1,
|
||||
convention='Test',
|
||||
term_raw='Test1',
|
||||
|
@ -22,7 +22,7 @@ class TestConstituentaAPI(EndpointTester):
|
|||
self.cst2 = Constituenta.objects.create(
|
||||
alias='X2',
|
||||
cst_type=CstType.BASE,
|
||||
schema=self.rsform_unowned,
|
||||
schema=self.rsform_unowned.model,
|
||||
order=1,
|
||||
convention='Test1',
|
||||
term_raw='Test2',
|
||||
|
@ -30,7 +30,7 @@ class TestConstituentaAPI(EndpointTester):
|
|||
)
|
||||
self.cst3 = Constituenta.objects.create(
|
||||
alias='X3',
|
||||
schema=self.rsform_owned,
|
||||
schema=self.rsform_owned.model,
|
||||
order=2,
|
||||
term_raw='Test3',
|
||||
term_resolved='Test3',
|
||||
|
|
|
@ -10,16 +10,16 @@ class TestInlineSynthesis(EndpointTester):
|
|||
@decl_endpoint('/api/operations/inline-synthesis', method='patch')
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.schema1 = RSForm.objects.create(title='Test1', alias='T1', owner=self.user)
|
||||
self.schema2 = RSForm.objects.create(title='Test2', alias='T2', owner=self.user)
|
||||
self.unowned = RSForm.objects.create(title='Test3', alias='T3')
|
||||
self.schema1 = RSForm.create(title='Test1', alias='T1', owner=self.user)
|
||||
self.schema2 = RSForm.create(title='Test2', alias='T2', owner=self.user)
|
||||
self.unowned = RSForm.create(title='Test3', alias='T3')
|
||||
|
||||
|
||||
def test_inline_synthesis_inputs(self):
|
||||
invalid_id = 1338
|
||||
data = {
|
||||
'receiver': self.unowned.pk,
|
||||
'source': self.schema1.pk,
|
||||
'receiver': self.unowned.model.pk,
|
||||
'source': self.schema1.model.pk,
|
||||
'items': [],
|
||||
'substitutions': []
|
||||
}
|
||||
|
@ -28,11 +28,11 @@ class TestInlineSynthesis(EndpointTester):
|
|||
data['receiver'] = invalid_id
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['receiver'] = self.schema1.pk
|
||||
data['receiver'] = self.schema1.model.pk
|
||||
data['source'] = invalid_id
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['source'] = self.schema1.pk
|
||||
data['source'] = self.schema1.model.pk
|
||||
self.executeOK(data=data)
|
||||
|
||||
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
|
||||
|
||||
data = {
|
||||
'receiver': self.schema1.pk,
|
||||
'source': self.schema2.pk,
|
||||
'receiver': self.schema1.model.pk,
|
||||
'source': self.schema2.model.pk,
|
||||
'items': [ks2_x1.pk, ks2_x2.pk, ks2_s1.pk, ks2_d1.pk],
|
||||
'substitutions': [
|
||||
{
|
||||
|
|
|
@ -6,15 +6,8 @@ from zipfile import ZipFile
|
|||
from cctext import ReferenceType
|
||||
from rest_framework import status
|
||||
|
||||
from apps.rsform.models import (
|
||||
AccessPolicy,
|
||||
Constituenta,
|
||||
CstType,
|
||||
LibraryItem,
|
||||
LibraryItemType,
|
||||
LocationHead,
|
||||
RSForm
|
||||
)
|
||||
from apps.library.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead
|
||||
from apps.rsform.models import Constituenta, CstType, RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
from shared.testing_utils import response_contains
|
||||
|
||||
|
@ -24,12 +17,12 @@ class TestRSFormViewset(EndpointTester):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.owned = RSForm.objects.create(title='Test', alias='T1', owner=self.user)
|
||||
self.owned_id = self.owned.pk
|
||||
self.unowned = RSForm.objects.create(title='Test2', alias='T2')
|
||||
self.unowned_id = self.unowned.pk
|
||||
self.private = RSForm.objects.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
||||
self.private_id = self.private.pk
|
||||
self.owned = RSForm.create(title='Test', alias='T1', owner=self.user)
|
||||
self.owned_id = self.owned.model.pk
|
||||
self.unowned = RSForm.create(title='Test2', alias='T2')
|
||||
self.unowned_id = self.unowned.model.pk
|
||||
self.private = RSForm.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
||||
self.private_id = self.private.model.pk
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/create-detailed', method='post')
|
||||
|
@ -57,25 +50,25 @@ class TestRSFormViewset(EndpointTester):
|
|||
|
||||
@decl_endpoint('/api/rsforms', method='get')
|
||||
def test_list_rsforms(self):
|
||||
non_schema = LibraryItem.objects.create(
|
||||
oss = LibraryItem.objects.create(
|
||||
item_type=LibraryItemType.OPERATION_SCHEMA,
|
||||
title='Test3'
|
||||
)
|
||||
response = self.executeOK()
|
||||
self.assertFalse(response_contains(response, non_schema))
|
||||
self.assertTrue(response_contains(response, self.unowned))
|
||||
self.assertTrue(response_contains(response, self.owned))
|
||||
self.assertFalse(response_contains(response, oss))
|
||||
self.assertTrue(response_contains(response, self.unowned.model))
|
||||
self.assertTrue(response_contains(response, self.owned.model))
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/contents', method='get')
|
||||
def test_contents(self):
|
||||
response = self.executeOK(item=self.owned_id)
|
||||
self.assertEqual(response.data['owner'], self.owned.owner.pk)
|
||||
self.assertEqual(response.data['title'], self.owned.title)
|
||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
||||
self.assertEqual(response.data['location'], self.owned.location)
|
||||
self.assertEqual(response.data['access_policy'], self.owned.access_policy)
|
||||
self.assertEqual(response.data['visible'], self.owned.visible)
|
||||
self.assertEqual(response.data['owner'], self.owned.model.owner.pk)
|
||||
self.assertEqual(response.data['title'], self.owned.model.title)
|
||||
self.assertEqual(response.data['alias'], self.owned.model.alias)
|
||||
self.assertEqual(response.data['location'], self.owned.model.location)
|
||||
self.assertEqual(response.data['access_policy'], self.owned.model.access_policy)
|
||||
self.assertEqual(response.data['visible'], self.owned.model.visible)
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/details', method='get')
|
||||
|
@ -92,12 +85,12 @@ class TestRSFormViewset(EndpointTester):
|
|||
)
|
||||
|
||||
response = self.executeOK(item=self.owned_id)
|
||||
self.assertEqual(response.data['owner'], self.owned.owner.pk)
|
||||
self.assertEqual(response.data['title'], self.owned.title)
|
||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
||||
self.assertEqual(response.data['location'], self.owned.location)
|
||||
self.assertEqual(response.data['access_policy'], self.owned.access_policy)
|
||||
self.assertEqual(response.data['visible'], self.owned.visible)
|
||||
self.assertEqual(response.data['owner'], self.owned.model.owner.pk)
|
||||
self.assertEqual(response.data['title'], self.owned.model.title)
|
||||
self.assertEqual(response.data['alias'], self.owned.model.alias)
|
||||
self.assertEqual(response.data['location'], self.owned.model.location)
|
||||
self.assertEqual(response.data['access_policy'], self.owned.model.access_policy)
|
||||
self.assertEqual(response.data['visible'], self.owned.model.visible)
|
||||
|
||||
self.assertEqual(len(response.data['items']), 2)
|
||||
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')
|
||||
def test_export_trs(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
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')
|
||||
with io.BytesIO(response.content) as stream:
|
||||
with ZipFile(stream, 'r') as zipped_file:
|
||||
|
@ -186,7 +179,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
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):
|
||||
data = {'alias': 'X3'}
|
||||
self.executeForbidden(data=data, item=self.unowned_id)
|
||||
|
@ -229,7 +222,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
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):
|
||||
x1 = self.owned.insert_new(
|
||||
alias='X1',
|
||||
|
@ -279,7 +272,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
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):
|
||||
x1 = self.owned.insert_new(
|
||||
alias='X1',
|
||||
|
@ -316,7 +309,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
self.assertEqual(d1.term_resolved, 'form1')
|
||||
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):
|
||||
self.set_params(item=self.owned_id)
|
||||
x1 = self.owned.insert_new('X1')
|
||||
|
@ -362,7 +355,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
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):
|
||||
data = {
|
||||
'alias': 'X3',
|
||||
|
@ -383,7 +376,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
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):
|
||||
self.set_params(item=self.owned_id)
|
||||
|
||||
|
@ -407,7 +400,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
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):
|
||||
self.set_params(item=self.owned_id)
|
||||
|
||||
|
@ -458,7 +451,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
@decl_endpoint('/api/rsforms/{item}/load-trs', method='patch')
|
||||
def test_load_trs(self):
|
||||
self.set_params(item=self.owned_id)
|
||||
self.owned.title = 'Test11'
|
||||
self.owned.model.title = 'Test11'
|
||||
self.owned.save()
|
||||
x1 = self.owned.insert_new('X1')
|
||||
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')
|
||||
self.owned.refresh_from_db()
|
||||
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(self.owned.constituents().count(), 25)
|
||||
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):
|
||||
self.set_params(item=self.owned_id)
|
||||
x1 = self.owned.insert_new('X1')
|
||||
|
|
|
@ -5,22 +5,14 @@ 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('rsforms', views.RSFormViewSet, 'RSForm')
|
||||
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('constituents/<int:pk>', views.ConstituentAPIView.as_view(), name='constituenta-detail'),
|
||||
path('rsforms/import-trs', views.TrsImportView.as_view()),
|
||||
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('rslang/parse-expression', views.parse_expression),
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
''' Utility functions '''
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
# Name for JSON inside Exteor files archive
|
||||
EXTEOR_INNER_FILENAME = 'document.json'
|
||||
|
@ -11,23 +8,6 @@ EXTEOR_INNER_FILENAME = 'document.json'
|
|||
_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:
|
||||
''' Apply mapping to matching in regular expression pattern subgroup 1 '''
|
||||
if text == '' or pattern == '':
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
''' REST API: Endpoint processors. '''
|
||||
from .cctext import generate_lexeme, inflect, parse_text
|
||||
from .constituents import ConstituentAPIView
|
||||
from .library import LibraryActiveView, LibraryAdminView, LibraryTemplatesView, LibraryViewSet
|
||||
from .operations import inline_synthesis
|
||||
from .rsforms import RSFormViewSet, TrsImportView, create_rsform
|
||||
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)
|
||||
|
||||
schema = cast(m.RSForm, serializer.validated_data['receiver'])
|
||||
receiver = m.RSForm(serializer.validated_data['receiver'])
|
||||
items = cast(list[m.Constituenta], serializer.validated_data['items'])
|
||||
|
||||
with transaction.atomic():
|
||||
new_items = schema.insert_copy(items)
|
||||
new_items = receiver.insert_copy(items)
|
||||
for substitution in serializer.validated_data['substitutions']:
|
||||
original = cast(m.Constituenta, substitution['original'])
|
||||
replacement = cast(m.Constituenta, substitution['substitution'])
|
||||
|
@ -41,10 +41,10 @@ def inline_synthesis(request: Request):
|
|||
else:
|
||||
index = next(i for (i, cst) in enumerate(items) if cst == replacement)
|
||||
replacement = new_items[index]
|
||||
schema.substitute(original, replacement, substitution['transfer_term'])
|
||||
schema.restore_order()
|
||||
receiver.substitute(original, replacement, substitution['transfer_term'])
|
||||
receiver.restore_order()
|
||||
|
||||
return Response(
|
||||
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.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 permissions
|
||||
from shared import permissions, utility
|
||||
|
||||
from .. import models as m
|
||||
from .. import serializers as s
|
||||
|
@ -25,21 +28,24 @@ from .. import utils
|
|||
@extend_schema_view()
|
||||
class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
||||
''' Endpoint: RSForm operations. '''
|
||||
queryset = m.RSForm.objects.all()
|
||||
serializer_class = s.LibraryItemSerializer
|
||||
queryset = LibraryItem.objects.filter(item_type=LibraryItemType.RSFORM)
|
||||
serializer_class = LibraryItemSerializer
|
||||
|
||||
def _get_schema(self) -> m.RSForm:
|
||||
return cast(m.RSForm, self.get_object())
|
||||
def _get_item(self) -> LibraryItem:
|
||||
return cast(LibraryItem, self.get_object())
|
||||
|
||||
def get_permissions(self):
|
||||
''' Determine permission class. '''
|
||||
if self.action in [
|
||||
'load_trs',
|
||||
'create_cst',
|
||||
'delete_multiple_cst',
|
||||
'rename_cst',
|
||||
'move_cst',
|
||||
'substitute',
|
||||
'restore_order',
|
||||
'reset_aliases',
|
||||
'cst_create',
|
||||
'cst_delete_multiple',
|
||||
'cst_rename',
|
||||
'cst_substitute'
|
||||
'produce_structure'
|
||||
]:
|
||||
permission_list = [permissions.ItemEditor]
|
||||
elif self.action in [
|
||||
|
@ -65,21 +71,21 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['post'], url_path='cst-create')
|
||||
def cst_create(self, request: Request, pk):
|
||||
@action(detail=True, methods=['post'], url_path='create-cst')
|
||||
def create_cst(self, request: Request, pk):
|
||||
''' Create new constituenta. '''
|
||||
schema = self._get_schema()
|
||||
schema = self._get_item()
|
||||
serializer = s.CstCreateSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
data = serializer.validated_data
|
||||
if 'insert_after' in data and data['insert_after'] is not None:
|
||||
try:
|
||||
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)
|
||||
else:
|
||||
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()
|
||||
response = Response(
|
||||
|
@ -103,10 +109,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
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):
|
||||
''' 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.is_valid(raise_exception=True)
|
||||
|
@ -120,7 +126,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
data={f'{cst.id}': msg.constituentaNoStructure()}
|
||||
)
|
||||
|
||||
result = schema.produce_structure(cst, cst_parse)
|
||||
result = m.RSForm(schema).produce_structure(cst, cst_parse)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data={
|
||||
|
@ -140,10 +146,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-rename')
|
||||
def cst_rename(self, request: Request, pk):
|
||||
@action(detail=True, methods=['patch'], url_path='rename-cst')
|
||||
def rename_cst(self, request: Request, pk):
|
||||
''' Rename constituenta possibly changing type. '''
|
||||
schema = self._get_schema()
|
||||
schema = self._get_item()
|
||||
serializer = s.CstRenameSerializer(data=request.data, context={'schema': schema})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
|
@ -155,10 +161,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
|
||||
with transaction.atomic():
|
||||
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()
|
||||
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data={
|
||||
|
@ -178,10 +184,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-substitute')
|
||||
def cst_substitute(self, request: Request, pk):
|
||||
@action(detail=True, methods=['patch'], url_path='substitute')
|
||||
def substitute(self, request: Request, pk):
|
||||
''' Substitute occurrences of constituenta with another one. '''
|
||||
schema = self._get_schema()
|
||||
schema = self._get_item()
|
||||
serializer = s.CstSubstituteSerializer(
|
||||
data=request.data,
|
||||
context={'schema': schema}
|
||||
|
@ -192,7 +198,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
for substitution in serializer.validated_data['substitutions']:
|
||||
original = cast(m.Constituenta, substitution['original'])
|
||||
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()
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
|
@ -210,16 +217,17 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-delete-multiple')
|
||||
def cst_delete_multiple(self, request: Request, pk):
|
||||
@action(detail=True, methods=['patch'], url_path='delete-multiple-cst')
|
||||
def delete_multiple_cst(self, request: Request, pk):
|
||||
''' Endpoint: Delete multiple constituents. '''
|
||||
schema = self._get_schema()
|
||||
schema = self._get_item()
|
||||
serializer = s.CstListSerializer(
|
||||
data=request.data,
|
||||
context={'schema': schema}
|
||||
)
|
||||
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()
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
|
@ -237,16 +245,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
||||
def cst_moveto(self, request: Request, pk):
|
||||
@action(detail=True, methods=['patch'], url_path='move-cst')
|
||||
def move_cst(self, request: Request, pk):
|
||||
''' Endpoint: Move multiple constituents. '''
|
||||
schema = self._get_schema()
|
||||
schema = self._get_item()
|
||||
serializer = s.CstMoveSerializer(
|
||||
data=request.data,
|
||||
context={'schema': schema}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema.move_cst(
|
||||
m.RSForm(schema).move_cst(
|
||||
listCst=serializer.validated_data['items'],
|
||||
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')
|
||||
def reset_aliases(self, request: Request, pk):
|
||||
''' Endpoint: Recreate all aliases based on order. '''
|
||||
schema = self._get_schema()
|
||||
schema.reset_aliases()
|
||||
schema = self._get_item()
|
||||
m.RSForm(schema).reset_aliases()
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
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')
|
||||
def restore_order(self, request: Request, pk):
|
||||
''' Endpoint: Restore order based on types and term graph. '''
|
||||
schema = self._get_schema()
|
||||
schema.restore_order()
|
||||
schema = self._get_item()
|
||||
m.RSForm(schema).restore_order()
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
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. '''
|
||||
input_serializer = s.RSFormUploadSerializer(data=request.data)
|
||||
input_serializer.is_valid(raise_exception=True)
|
||||
schema = self._get_schema()
|
||||
|
||||
schema = self._get_item()
|
||||
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
|
||||
|
||||
serializer = s.RSFormTRSSerializer(
|
||||
|
@ -321,10 +330,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
context={'load_meta': load_metadata}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
result = serializer.save()
|
||||
result: m.RSForm = serializer.save()
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.RSFormParseSerializer(result).data
|
||||
data=s.RSFormParseSerializer(result.model).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -339,10 +348,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
@action(detail=True, methods=['get'], url_path='contents')
|
||||
def contents(self, request: Request, pk):
|
||||
''' Endpoint: View schema db contents (including constituents). '''
|
||||
schema = s.RSFormSerializer(self.get_object())
|
||||
serializer = s.RSFormSerializer(self.get_object())
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=schema.data
|
||||
data=serializer.data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -357,7 +366,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
@action(detail=True, methods=['get'], url_path='details')
|
||||
def details(self, request: Request, pk):
|
||||
''' Endpoint: Detailed schema view including statuses and parse. '''
|
||||
serializer = s.RSFormParseSerializer(self._get_schema())
|
||||
serializer = s.RSFormParseSerializer(self.get_object())
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=serializer.data
|
||||
|
@ -378,8 +387,8 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
serializer = s.ExpressionSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
expression = serializer.validated_data['expression']
|
||||
schema = s.PyConceptAdapter(self._get_schema())
|
||||
result = pyconcept.check_expression(json.dumps(schema.data), expression)
|
||||
pySchema = s.PyConceptAdapter(m.RSForm(self.get_object()))
|
||||
result = pyconcept.check_expression(json.dumps(pySchema.data), expression)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=json.loads(result)
|
||||
|
@ -400,7 +409,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
serializer = s.TextSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
text = serializer.validated_data['text']
|
||||
resolver = self._get_schema().resolver()
|
||||
resolver = m.RSForm(self.get_object()).resolver()
|
||||
resolver.resolve(text)
|
||||
return Response(
|
||||
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')
|
||||
def export_trs(self, request: Request, pk):
|
||||
''' Endpoint: Download Exteor compatible file. '''
|
||||
data = s.RSFormTRSSerializer(self._get_schema()).data
|
||||
file = utils.write_zipped_json(data, utils.EXTEOR_INNER_FILENAME)
|
||||
filename = utils.filename_for_schema(self._get_schema().alias)
|
||||
schema = self._get_item()
|
||||
data = s.RSFormTRSSerializer(m.RSForm(schema)).data
|
||||
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['Content-Disposition'] = f'attachment; filename={filename}'
|
||||
return response
|
||||
|
@ -437,33 +447,32 @@ class TrsImportView(views.APIView):
|
|||
tags=['RSForm'],
|
||||
request=s.FileSerializer,
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.LibraryItemSerializer,
|
||||
c.HTTP_201_CREATED: LibraryItemSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None
|
||||
}
|
||||
)
|
||||
def post(self, request: Request):
|
||||
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
||||
owner = cast(m.User, self.request.user)
|
||||
data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
||||
owner = cast(User, self.request.user)
|
||||
_prepare_rsform_data(data, request, owner)
|
||||
serializer = s.RSFormTRSSerializer(
|
||||
data=data,
|
||||
context={'load_meta': True}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema = serializer.save()
|
||||
result = s.LibraryItemSerializer(schema)
|
||||
schema: m.RSForm = serializer.save()
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data=result.data
|
||||
data=LibraryItemSerializer(schema.model).data
|
||||
)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='create new RSForm empty or from file',
|
||||
tags=['RSForm'],
|
||||
request=s.LibraryItemSerializer,
|
||||
request=LibraryItemSerializer,
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.LibraryItemSerializer,
|
||||
c.HTTP_201_CREATED: LibraryItemSerializer,
|
||||
c.HTTP_400_BAD_REQUEST: None,
|
||||
c.HTTP_403_FORBIDDEN: None
|
||||
}
|
||||
|
@ -471,26 +480,25 @@ class TrsImportView(views.APIView):
|
|||
@api_view(['POST'])
|
||||
def create_rsform(request: Request):
|
||||
''' 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:
|
||||
return Response(
|
||||
status=c.HTTP_400_BAD_REQUEST,
|
||||
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)
|
||||
serializer_rsform = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
||||
serializer_rsform.is_valid(raise_exception=True)
|
||||
schema = serializer_rsform.save()
|
||||
result = s.LibraryItemSerializer(schema)
|
||||
schema: m.RSForm = serializer_rsform.save()
|
||||
return Response(
|
||||
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
|
||||
if 'title' in request.data and 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'
|
||||
data['read_only'] = read_only
|
||||
|
||||
data['access_policy'] = request.data.get('access_policy', m.AccessPolicy.PUBLIC)
|
||||
data['location'] = request.data.get('location', m.LocationHead.USER)
|
||||
data['access_policy'] = request.data.get('access_policy', AccessPolicy.PUBLIC)
|
||||
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 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 . import models
|
||||
|
|
|
@ -4668,7 +4668,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.editor",
|
||||
"model": "library.editor",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"item": 35,
|
||||
|
@ -4677,7 +4677,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.subscription",
|
||||
"model": "library.subscription",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"user": 1,
|
||||
|
@ -4685,7 +4685,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.subscription",
|
||||
"model": "library.subscription",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"user": 5,
|
||||
|
@ -4693,7 +4693,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.subscription",
|
||||
"model": "library.subscription",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"user": 3,
|
||||
|
@ -4701,7 +4701,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.subscription",
|
||||
"model": "library.subscription",
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"user": 3,
|
||||
|
@ -4709,7 +4709,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.libraryitem",
|
||||
"model": "library.libraryitem",
|
||||
"pk": 34,
|
||||
"fields": {
|
||||
"item_type": "rsform",
|
||||
|
@ -4726,7 +4726,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.libraryitem",
|
||||
"model": "library.libraryitem",
|
||||
"pk": 35,
|
||||
"fields": {
|
||||
"item_type": "rsform",
|
||||
|
@ -4743,7 +4743,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.libraryitem",
|
||||
"model": "library.libraryitem",
|
||||
"pk": 36,
|
||||
"fields": {
|
||||
"item_type": "rsform",
|
||||
|
@ -4760,7 +4760,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.libraryitem",
|
||||
"model": "library.libraryitem",
|
||||
"pk": 37,
|
||||
"fields": {
|
||||
"item_type": "rsform",
|
||||
|
@ -4777,7 +4777,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"model": "rsform.librarytemplate",
|
||||
"model": "library.librarytemplate",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"lib_source": 34
|
||||
|
|
|
@ -73,6 +73,7 @@ INSTALLED_APPS = [
|
|||
'corsheaders',
|
||||
|
||||
'apps.users',
|
||||
'apps.library',
|
||||
'apps.rsform',
|
||||
'apps.oss',
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, Spec
|
|||
|
||||
urlpatterns = [
|
||||
path('admin', admin.site.urls),
|
||||
path('api/', include('apps.library.urls')),
|
||||
path('api/', include('apps.rsform.urls')),
|
||||
path('api/', include('apps.oss.urls')),
|
||||
path('users/', include('apps.users.urls')),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from rest_framework import status
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -11,15 +11,9 @@ from rest_framework.permissions import \
|
|||
from rest_framework.request import Request
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from apps.library.models import AccessPolicy, Editor, LibraryItem, Subscription, Version
|
||||
from apps.oss.models import Operation
|
||||
from apps.rsform.models import (
|
||||
AccessPolicy,
|
||||
Constituenta,
|
||||
Editor,
|
||||
LibraryItem,
|
||||
Subscription,
|
||||
Version
|
||||
)
|
||||
from apps.rsform.models import Constituenta
|
||||
from apps.users.models import User
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
''' Utilities for testing. '''
|
||||
|
||||
from apps.rsform.models import LibraryItem
|
||||
from apps.library.models import LibraryItem
|
||||
|
||||
|
||||
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",
|
||||
"axios": "^1.7.2",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.3.8",
|
||||
"framer-motion": "^11.3.17",
|
||||
"html-to-image": "^1.11.11",
|
||||
"js-file-download": "^0.4.12",
|
||||
"react": "^18.3.1",
|
||||
|
@ -36,24 +36,24 @@
|
|||
"devDependencies": {
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.14.11",
|
||||
"@types/node": "^20.14.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
||||
"@typescript-eslint/parser": "^7.16.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"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-tsdoc": "^0.3.0",
|
||||
"jest": "^29.7.0",
|
||||
"postcss": "^8.4.39",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"postcss": "^8.4.40",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"ts-jest": "^29.2.3",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.3.4"
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.3.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
|
@ -740,9 +740,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.6.tgz",
|
||||
"integrity": "sha512-bhwB1AZ6zU4M3dNKm8Aa2BXwj5mWDqE9IWpqxYKJoLCnx+AcwcMuLO01tLWgc1mx4vT1IVYVqx86YoqUsATrqQ==",
|
||||
"version": "6.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.29.0.tgz",
|
||||
"integrity": "sha512-ED4ims4fkf7eOA+HYLVP8VVg3NMllt1FPm9PEJBfYFnidKlRITBaua38u68L1F60eNtw2YNcDN5jsIzhKZwWQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.4.0",
|
||||
|
@ -797,14 +797,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@emotion/cache": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.12.0.tgz",
|
||||
"integrity": "sha512-VFo/F1PthkxHwWDCcXkidyXw70eAkdiNiCzthMI2rRQjFiTvmXt8UDlv/VE1DTsd4CIEY2wQf5AnL2QiPgphlw==",
|
||||
"version": "11.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz",
|
||||
"integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/sheet": "^1.3.0",
|
||||
"@emotion/utils": "^1.3.0",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"@emotion/utils": "^1.4.0",
|
||||
"@emotion/weak-memoize": "^0.4.0",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
|
@ -837,17 +837,17 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/react": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.12.0.tgz",
|
||||
"integrity": "sha512-kTktYMpG8mHjLi8u6XOTMfDmQvUve/un2ZVj4khcU2KTn17ElMV8BK6QFzT8V/v2QW8013rf07Yc0ayQL3tp3w==",
|
||||
"version": "11.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz",
|
||||
"integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.12.0",
|
||||
"@emotion/cache": "^11.12.0",
|
||||
"@emotion/serialize": "^1.2.0",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
|
||||
"@emotion/utils": "^1.3.0",
|
||||
"@emotion/cache": "^11.13.0",
|
||||
"@emotion/serialize": "^1.3.0",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
|
||||
"@emotion/utils": "^1.4.0",
|
||||
"@emotion/weak-memoize": "^0.4.0",
|
||||
"hoist-non-react-statics": "^3.3.1"
|
||||
},
|
||||
|
@ -861,22 +861,22 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@emotion/serialize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.2.0.tgz",
|
||||
"integrity": "sha512-X5UWpZAhGGp5LOn7OAI9k9JjRtz7nSFhZypatADcuEd/0bECZ0DzVjPdL8hljTrAku8+TjFvWIYHMOCO/0v/Ng==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz",
|
||||
"integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/hash": "^0.9.2",
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/unitless": "^0.9.0",
|
||||
"@emotion/utils": "^1.3.0",
|
||||
"@emotion/utils": "^1.4.0",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/sheet": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.3.0.tgz",
|
||||
"integrity": "sha512-vOPwbKw8fj/oSEa7CWqiKCvLZ1AeLIAApmboGP34xUyUjXalFyf+tMtgMDqP7VMevLPhUa+YWJS46cQUA+tr9A==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/unitless": {
|
||||
|
@ -886,18 +886,18 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
|
||||
"integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
|
||||
"integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/utils": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.3.0.tgz",
|
||||
"integrity": "sha512-+M7u4EaX5t4bCunKTltAdGis3NFHQniikLVEQ+rPQccsX/xV4v5Etwg12paioZ9DsO+CTvimtmnjZbW85kbF8Q==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz",
|
||||
"integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/weak-memoize": {
|
||||
|
@ -1411,28 +1411,28 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz",
|
||||
"integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz",
|
||||
"integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.4"
|
||||
"@floating-ui/utils": "^0.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz",
|
||||
"integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==",
|
||||
"version": "1.6.8",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz",
|
||||
"integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.4"
|
||||
"@floating-ui/utils": "^0.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
|
||||
"integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==",
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz",
|
||||
"integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
|
@ -2969,9 +2969,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
|
||||
"integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz",
|
||||
"integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
@ -2983,9 +2983,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz",
|
||||
"integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz",
|
||||
"integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -2997,9 +2997,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz",
|
||||
"integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz",
|
||||
"integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -3011,9 +3011,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz",
|
||||
"integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz",
|
||||
"integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -3025,9 +3025,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz",
|
||||
"integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz",
|
||||
"integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
@ -3039,9 +3039,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz",
|
||||
"integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz",
|
||||
"integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
@ -3053,9 +3053,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz",
|
||||
"integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -3067,9 +3067,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz",
|
||||
"integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz",
|
||||
"integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -3081,9 +3081,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz",
|
||||
"integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
|
@ -3095,9 +3095,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz",
|
||||
"integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
|
@ -3109,9 +3109,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz",
|
||||
"integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
|
@ -3123,9 +3123,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz",
|
||||
"integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -3137,9 +3137,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz",
|
||||
"integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz",
|
||||
"integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -3151,9 +3151,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz",
|
||||
"integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -3165,9 +3165,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz",
|
||||
"integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
@ -3179,9 +3179,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz",
|
||||
"integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -3253,9 +3253,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tweenjs/tween.js": {
|
||||
"version": "23.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.2.tgz",
|
||||
"integrity": "sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ==",
|
||||
"version": "23.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
|
@ -3634,9 +3634,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.14.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
|
||||
"integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
|
||||
"version": "20.14.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz",
|
||||
"integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -3719,9 +3719,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.166.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.166.0.tgz",
|
||||
"integrity": "sha512-FHMnpcdhdbdOOIYbfkTkUVpYMW53odxbTRwd0/xJpYnTzEsjnVnondGAvHZb4z06UW0vo6WPVuvH0/9qrxKx7g==",
|
||||
"version": "0.167.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.0.tgz",
|
||||
"integrity": "sha512-BC+Vbm0d6yMzct7dhTBe9ZjEh6ygupyn1k/UcZncIIS/5aNIbfvF77gQw1IFP09Oyj1UxWj0EUBBqc1GkqzsOw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
@ -3756,17 +3756,17 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "7.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz",
|
||||
"integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==",
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz",
|
||||
"integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "7.16.1",
|
||||
"@typescript-eslint/type-utils": "7.16.1",
|
||||
"@typescript-eslint/utils": "7.16.1",
|
||||
"@typescript-eslint/visitor-keys": "7.16.1",
|
||||
"@typescript-eslint/scope-manager": "7.17.0",
|
||||
"@typescript-eslint/type-utils": "7.17.0",
|
||||
"@typescript-eslint/utils": "7.17.0",
|
||||
"@typescript-eslint/visitor-keys": "7.17.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
|
@ -3790,16 +3790,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "7.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz",
|
||||
"integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==",
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz",
|
||||
"integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "7.16.1",
|
||||
"@typescript-eslint/types": "7.16.1",
|
||||
"@typescript-eslint/typescript-estree": "7.16.1",
|
||||
"@typescript-eslint/visitor-keys": "7.16.1",
|
||||
"@typescript-eslint/scope-manager": "7.17.0",
|
||||
"@typescript-eslint/types": "7.17.0",
|
||||
"@typescript-eslint/typescript-estree": "7.17.0",
|
||||
"@typescript-eslint/visitor-keys": "7.17.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -3819,14 +3819,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "7.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz",
|
||||
"integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==",
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz",
|
||||
"integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.16.1",
|
||||
"@typescript-eslint/visitor-keys": "7.16.1"
|
||||
"@typescript-eslint/types": "7.17.0",
|
||||
"@typescript-eslint/visitor-keys": "7.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
|
@ -3837,14 +3837,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "7.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz",
|
||||
"integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==",
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz",
|
||||
"integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "7.16.1",
|
||||
"@typescript-eslint/utils": "7.16.1",
|
||||
"@typescript-eslint/typescript-estree": "7.17.0",
|
||||
"@typescript-eslint/utils": "7.17.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
|
@ -3865,9 +3865,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "7.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz",
|
||||
"integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==",
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz",
|
||||
"integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -3879,14 +3879,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "7.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz",
|
||||
"integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==",
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz",
|
||||
"integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.16.1",
|
||||
"@typescript-eslint/visitor-keys": "7.16.1",
|
||||
"@typescript-eslint/types": "7.17.0",
|
||||
"@typescript-eslint/visitor-keys": "7.17.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
|
@ -3908,16 +3908,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "7.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz",
|
||||
"integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==",
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz",
|
||||
"integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "7.16.1",
|
||||
"@typescript-eslint/types": "7.16.1",
|
||||
"@typescript-eslint/typescript-estree": "7.16.1"
|
||||
"@typescript-eslint/scope-manager": "7.17.0",
|
||||
"@typescript-eslint/types": "7.17.0",
|
||||
"@typescript-eslint/typescript-estree": "7.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
|
@ -3931,13 +3931,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "7.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz",
|
||||
"integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==",
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz",
|
||||
"integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.16.1",
|
||||
"@typescript-eslint/types": "7.17.0",
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -4675,9 +4675,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001642",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz",
|
||||
"integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==",
|
||||
"version": "1.0.30001643",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
|
||||
"integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -5449,9 +5449,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/detect-gpu": {
|
||||
"version": "5.0.39",
|
||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.39.tgz",
|
||||
"integrity": "sha512-qs+7gnNNxsH4RN1IPpQieU2XNO+RhgemuaRhcawiUug6oXb0Glup90H1YGSjslPO30Sw0E4yfjRoGtSEURwVPQ==",
|
||||
"version": "5.0.40",
|
||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.40.tgz",
|
||||
"integrity": "sha512-5v4jDN/ERdZZitD29UiLjV9Q9+lDfw2OhEJACIqnvdWulVZCy2K6EwonZ/VKyo4YMqvSIzGIDmojX3jGL3dLpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"webgl-constants": "^1.1.1"
|
||||
|
@ -5567,9 +5567,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.830",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.830.tgz",
|
||||
"integrity": "sha512-TrPKKH20HeN0J1LHzsYLs2qwXrp8TF4nHdu4sq61ozGbzMpWhI7iIOPYPPkxeq1azMT9PZ8enPFcftbs/Npcjg==",
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.1.tgz",
|
||||
"integrity": "sha512-FKbOCOQ5QRB3VlIbl1LZQefWIYwszlBloaXcY2rbfpu9ioJnNh3TK03YtIDKDo3WKBi8u+YV4+Fn2CkEozgf4w==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
@ -5736,9 +5736,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-refresh": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.8.tgz",
|
||||
"integrity": "sha512-MIKAclwaDFIiYtVBLzDdm16E+Ty4GwhB6wZlCAG1R3Ur+F9Qbo6PRxpA5DK7XtDgm+WlCoAY2WxAwqhmIDHg6Q==",
|
||||
"version": "0.4.9",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.9.tgz",
|
||||
"integrity": "sha512-QK49YrBAo5CLNLseZ7sZgvgTy21E6NEw22eZqc4teZfH8pxV3yXc9XXOYfUI6JNpw7mfHNkAeWtBxrTyykB6HA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
|
@ -6325,9 +6325,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "11.3.8",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.8.tgz",
|
||||
"integrity": "sha512-1D+RDTsIp4Rz2dq/oToqSEc9idEQwgBRQyBq4rGpFba+0Z+GCbj9z1s0+ikFbanWe3YJ0SqkNlDe08GcpFGj5A==",
|
||||
"version": "11.3.17",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.17.tgz",
|
||||
"integrity": "sha512-LZcckvZL8Rjod03bud8LQcp+R0PLmWIlOSu+NVc+v6Uh43fQr4IBsEAX7sSn7CdBQ1L0fZ/IqSXZVPnGFSMxHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
|
@ -6866,9 +6866,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/import-local": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
|
||||
"integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
||||
"integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -9499,9 +9499,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz",
|
||||
"integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==",
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -9925,9 +9925,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.39",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
||||
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
||||
"version": "8.4.40",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
|
||||
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -10041,9 +10041,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-load-config/node_modules/yaml": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
|
||||
"integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
|
||||
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
|
@ -10054,21 +10054,27 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-nested": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
|
||||
"integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.11"
|
||||
"postcss-selector-parser": "^6.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.2.14"
|
||||
}
|
||||
|
@ -10776,9 +10782,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz",
|
||||
"integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.0.tgz",
|
||||
"integrity": "sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -10792,22 +10798,22 @@
|
|||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.18.1",
|
||||
"@rollup/rollup-android-arm64": "4.18.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.18.1",
|
||||
"@rollup/rollup-darwin-x64": "4.18.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.18.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.18.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.18.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.18.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.18.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.18.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.18.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.19.0",
|
||||
"@rollup/rollup-android-arm64": "4.19.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.19.0",
|
||||
"@rollup/rollup-darwin-x64": "4.19.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.19.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.19.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.19.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.19.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.19.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.19.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.19.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.19.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.19.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.19.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.19.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.19.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
|
@ -11346,9 +11352,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.6",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz",
|
||||
"integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==",
|
||||
"version": "3.4.7",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz",
|
||||
"integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -11708,9 +11714,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
||||
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -11849,9 +11855,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz",
|
||||
"integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==",
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
||||
"integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"@uiw/react-codemirror": "^4.23.0",
|
||||
"axios": "^1.7.2",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.3.8",
|
||||
"framer-motion": "^11.3.17",
|
||||
"html-to-image": "^1.11.11",
|
||||
"js-file-download": "^0.4.12",
|
||||
"react": "^18.3.1",
|
||||
|
@ -40,24 +40,24 @@
|
|||
"devDependencies": {
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.14.11",
|
||||
"@types/node": "^20.14.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
||||
"@typescript-eslint/parser": "^7.16.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"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-tsdoc": "^0.3.0",
|
||||
"jest": "^29.7.0",
|
||||
"postcss": "^8.4.39",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"postcss": "^8.4.40",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"ts-jest": "^29.2.3",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.3.4"
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.3.5"
|
||||
},
|
||||
"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