mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Change data model and refactor backend
This commit is contained in:
parent
5d45094640
commit
fba16a3d0b
|
@ -5,6 +5,7 @@ Param(
|
|||
|
||||
$pyExec = "$PSScriptRoot\backend\venv\Scripts\python.exe"
|
||||
$djangoSrc = "$PSScriptRoot\backend\manage.py"
|
||||
$initialData = "fixtures/InitialData.json"
|
||||
|
||||
function RunServer() {
|
||||
RunBackend
|
||||
|
@ -20,6 +21,7 @@ function RunBackend() {
|
|||
FlushData
|
||||
DoMigrations
|
||||
PrepareStatic -clearPrevious
|
||||
AddInitialData
|
||||
AddAdmin
|
||||
} else {
|
||||
DoMigrations
|
||||
|
@ -35,13 +37,16 @@ function RunFrontend() {
|
|||
}
|
||||
|
||||
function FlushData {
|
||||
& $pyExec $djangoSrc flush --no-input\
|
||||
& $pyExec $djangoSrc flush --noinput
|
||||
$dbPath = "$PSScriptRoot\backend\db.sqlite3"
|
||||
if (Test-Path -Path $dbPath -PathType Leaf) {
|
||||
Remove-Item $dbPath
|
||||
}
|
||||
}
|
||||
|
||||
function AddInitialData {
|
||||
& $pyExec manage.py loaddata $initialData
|
||||
}
|
||||
function AddAdmin {
|
||||
$env:DJANGO_SUPERUSER_USERNAME = 'admin'
|
||||
$env:DJANGO_SUPERUSER_PASSWORD = '1234'
|
||||
|
|
|
@ -8,9 +8,9 @@ class ConstituentaAdmin(admin.ModelAdmin):
|
|||
''' Admin model: Constituenta. '''
|
||||
|
||||
|
||||
class RSFormAdmin(admin.ModelAdmin):
|
||||
''' Admin model: RSForm. '''
|
||||
class Librarydmin(admin.ModelAdmin):
|
||||
''' Admin model: LibraryItem. '''
|
||||
|
||||
|
||||
admin.site.register(models.Constituenta, ConstituentaAdmin)
|
||||
admin.site.register(models.RSForm, RSFormAdmin)
|
||||
admin.site.register(models.LibraryItem, Librarydmin)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.3 on 2023-07-26 15:19
|
||||
# Generated by Django 4.2.4 on 2023-08-25 12:15
|
||||
|
||||
import apps.rsform.models
|
||||
from django.conf import settings
|
||||
|
@ -17,13 +17,15 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RSForm',
|
||||
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='Владелец')),
|
||||
|
@ -33,6 +35,18 @@ class Migration(migrations.Migration):
|
|||
'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': 'Подписка',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Constituenta',
|
||||
fields=[
|
||||
|
@ -47,7 +61,7 @@ class Migration(migrations.Migration):
|
|||
('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.rsform', verbose_name='Концептуальная схема')),
|
||||
('schema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.libraryitem', verbose_name='Концептуальная схема')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Конституета',
|
||||
|
|
|
@ -24,6 +24,12 @@ _REF_ENTITY_PATTERN = re.compile(r'@{([^0-9\-].*?)\|.*?}')
|
|||
_GLOBAL_ID_PATTERN = re.compile(r'([XCSADFPT][0-9]+)')
|
||||
|
||||
|
||||
class LibraryItemType(TextChoices):
|
||||
''' Type of library items '''
|
||||
RSFORM = 'rsform'
|
||||
OPERATIONS_SCHEMA = 'oss'
|
||||
|
||||
|
||||
class CstType(TextChoices):
|
||||
''' Type of constituenta '''
|
||||
BASE = 'basic'
|
||||
|
@ -67,8 +73,14 @@ def _get_type_prefix(cst_type: CstType) -> str:
|
|||
return 'X'
|
||||
|
||||
|
||||
class RSForm(Model):
|
||||
''' RSForm is a math form of capturing conceptual schema '''
|
||||
class LibraryItem(Model):
|
||||
''' Abstract library item.
|
||||
Please use wrappers below to access functionality. '''
|
||||
item_type: CharField = CharField(
|
||||
verbose_name='Тип',
|
||||
max_length=50,
|
||||
choices=LibraryItemType.choices
|
||||
)
|
||||
owner: ForeignKey = ForeignKey(
|
||||
verbose_name='Владелец',
|
||||
to=User,
|
||||
|
@ -91,6 +103,10 @@ class RSForm(Model):
|
|||
verbose_name='Общая',
|
||||
default=False
|
||||
)
|
||||
is_canonical: BooleanField = BooleanField(
|
||||
verbose_name='Каноничная',
|
||||
default=False
|
||||
)
|
||||
time_create: DateTimeField = DateTimeField(
|
||||
verbose_name='Дата создания',
|
||||
auto_now_add=True
|
||||
|
@ -105,9 +121,127 @@ class RSForm(Model):
|
|||
verbose_name = 'Схема'
|
||||
verbose_name_plural = 'Схемы'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.title}'
|
||||
|
||||
def get_absolute_url(self):
|
||||
return f'/api/library/{self.pk}/'
|
||||
|
||||
|
||||
class Subscription(Model):
|
||||
''' User subscription to library item. '''
|
||||
user: ForeignKey = ForeignKey(
|
||||
verbose_name='Пользователь',
|
||||
to=User,
|
||||
on_delete=CASCADE
|
||||
)
|
||||
item: ForeignKey = ForeignKey(
|
||||
verbose_name='Элемент',
|
||||
to=LibraryItem,
|
||||
on_delete=CASCADE
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Подписки'
|
||||
verbose_name_plural = 'Подписка'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.user} -> {self.item}'
|
||||
|
||||
|
||||
class Constituenta(Model):
|
||||
''' Constituenta is the base unit for every conceptual schema '''
|
||||
schema: ForeignKey = ForeignKey(
|
||||
verbose_name='Концептуальная схема',
|
||||
to=LibraryItem,
|
||||
on_delete=CASCADE
|
||||
)
|
||||
order: PositiveIntegerField = PositiveIntegerField(
|
||||
verbose_name='Позиция',
|
||||
validators=[MinValueValidator(1)],
|
||||
default=-1,
|
||||
)
|
||||
alias: CharField = CharField(
|
||||
verbose_name='Имя',
|
||||
max_length=8,
|
||||
default='undefined'
|
||||
)
|
||||
cst_type: CharField = CharField(
|
||||
verbose_name='Тип',
|
||||
max_length=10,
|
||||
choices=CstType.choices,
|
||||
default=CstType.BASE
|
||||
)
|
||||
convention: TextField = TextField(
|
||||
verbose_name='Комментарий/Конвенция',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
term_raw: TextField = TextField(
|
||||
verbose_name='Термин (с отсылками)',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
term_resolved: TextField = TextField(
|
||||
verbose_name='Термин',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
term_forms: JSONField = JSONField(
|
||||
verbose_name='Словоформы',
|
||||
default=_empty_forms
|
||||
)
|
||||
definition_formal: TextField = TextField(
|
||||
verbose_name='Родоструктурное определение',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
definition_raw: TextField = TextField(
|
||||
verbose_name='Текстовое определние (с отсылками)',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
definition_resolved: TextField = TextField(
|
||||
verbose_name='Текстовое определние',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Конституета'
|
||||
verbose_name_plural = 'Конституенты'
|
||||
|
||||
def get_absolute_url(self):
|
||||
''' URL access. '''
|
||||
return reverse('constituenta-detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.alias}'
|
||||
|
||||
def set_term_resolved(self, new_term: str):
|
||||
''' Set term and reset forms if needed. '''
|
||||
if new_term == self.term_resolved:
|
||||
return
|
||||
self.term_resolved = new_term
|
||||
self.term_forms = []
|
||||
|
||||
|
||||
class RSForm:
|
||||
''' RSForm is a math form of capturing conceptual schema. '''
|
||||
def __init__(self, item: LibraryItem):
|
||||
if item.item_type != LibraryItemType.RSFORM:
|
||||
raise ValueError('Attempting to use invalid adaptor for non-RSForm item')
|
||||
self.item = item
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs) -> 'RSForm':
|
||||
return RSForm(LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, **kwargs))
|
||||
|
||||
def constituents(self) -> QuerySet['Constituenta']:
|
||||
''' Get QuerySet containing all constituents of current RSForm '''
|
||||
return Constituenta.objects.filter(schema=self)
|
||||
''' Get QuerySet containing all constituents of current RSForm. '''
|
||||
return Constituenta.objects.filter(schema=self.item)
|
||||
|
||||
def resolver(self) -> Resolver:
|
||||
''' Create resolver for text references based on schema terms. '''
|
||||
|
@ -152,19 +286,19 @@ class RSForm(Model):
|
|||
''' Insert new constituenta at given position. All following constituents order is shifted by 1 position '''
|
||||
if position <= 0:
|
||||
raise ValidationError('Invalid position: should be positive integer')
|
||||
update_list = Constituenta.objects.only('id', 'order', 'schema').filter(schema=self, order__gte=position)
|
||||
update_list = Constituenta.objects.only('id', 'order', 'schema').filter(schema=self.item, order__gte=position)
|
||||
for cst in update_list:
|
||||
cst.order += 1
|
||||
Constituenta.objects.bulk_update(update_list, ['order'])
|
||||
|
||||
result = Constituenta.objects.create(
|
||||
schema=self,
|
||||
schema=self.item,
|
||||
order=position,
|
||||
alias=alias,
|
||||
cst_type=insert_type
|
||||
)
|
||||
self.update_order()
|
||||
self.save()
|
||||
self.item.save()
|
||||
result.refresh_from_db()
|
||||
return result
|
||||
|
||||
|
@ -175,13 +309,13 @@ class RSForm(Model):
|
|||
if self.constituents().exists():
|
||||
position += self.constituents().count()
|
||||
result = Constituenta.objects.create(
|
||||
schema=self,
|
||||
schema=self.item,
|
||||
order=position,
|
||||
alias=alias,
|
||||
cst_type=insert_type
|
||||
)
|
||||
self.update_order()
|
||||
self.save()
|
||||
self.item.save()
|
||||
result.refresh_from_db()
|
||||
return result
|
||||
|
||||
|
@ -207,7 +341,7 @@ class RSForm(Model):
|
|||
update_list.append(cst)
|
||||
Constituenta.objects.bulk_update(update_list, ['order'])
|
||||
self.update_order()
|
||||
self.save()
|
||||
self.item.save()
|
||||
|
||||
@transaction.atomic
|
||||
def delete_cst(self, listCst):
|
||||
|
@ -216,7 +350,7 @@ class RSForm(Model):
|
|||
cst.delete()
|
||||
self.update_order()
|
||||
self.resolve_all_text()
|
||||
self.save()
|
||||
self.item.save()
|
||||
|
||||
@transaction.atomic
|
||||
def create_cst(self, data: dict, insert_after: Optional[str]=None) -> 'Constituenta':
|
||||
|
@ -326,12 +460,6 @@ class RSForm(Model):
|
|||
else:
|
||||
return self.insert_last(data['alias'], data['cst_type'])
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.title}'
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('rsform-detail', kwargs={'pk': self.pk})
|
||||
|
||||
def _term_graph(self) -> Graph:
|
||||
result = Graph()
|
||||
cst_list = self.constituents().only('order', 'alias', 'term_raw').order_by('order')
|
||||
|
@ -355,83 +483,6 @@ class RSForm(Model):
|
|||
return result
|
||||
|
||||
|
||||
class Constituenta(Model):
|
||||
''' Constituenta is the base unit for every conceptual schema '''
|
||||
schema: ForeignKey = ForeignKey(
|
||||
verbose_name='Концептуальная схема',
|
||||
to=RSForm,
|
||||
on_delete=CASCADE
|
||||
)
|
||||
order: PositiveIntegerField = PositiveIntegerField(
|
||||
verbose_name='Позиция',
|
||||
validators=[MinValueValidator(1)],
|
||||
default=-1,
|
||||
)
|
||||
alias: CharField = CharField(
|
||||
verbose_name='Имя',
|
||||
max_length=8,
|
||||
default='undefined'
|
||||
)
|
||||
cst_type: CharField = CharField(
|
||||
verbose_name='Тип',
|
||||
max_length=10,
|
||||
choices=CstType.choices,
|
||||
default=CstType.BASE
|
||||
)
|
||||
convention: TextField = TextField(
|
||||
verbose_name='Комментарий/Конвенция',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
term_raw: TextField = TextField(
|
||||
verbose_name='Термин (с отсылками)',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
term_resolved: TextField = TextField(
|
||||
verbose_name='Термин',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
term_forms: JSONField = JSONField(
|
||||
verbose_name='Словоформы',
|
||||
default=_empty_forms
|
||||
)
|
||||
definition_formal: TextField = TextField(
|
||||
verbose_name='Родоструктурное определение',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
definition_raw: TextField = TextField(
|
||||
verbose_name='Текстовое определние (с отсылками)',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
definition_resolved: TextField = TextField(
|
||||
verbose_name='Текстовое определние',
|
||||
default='',
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Конституета'
|
||||
verbose_name_plural = 'Конституенты'
|
||||
|
||||
def get_absolute_url(self):
|
||||
''' URL access. '''
|
||||
return reverse('constituenta-detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.alias}'
|
||||
|
||||
def set_term_resolved(self, new_term: str):
|
||||
''' Set term and reset forms if needed. '''
|
||||
if new_term == self.term_resolved:
|
||||
return
|
||||
self.term_resolved = new_term
|
||||
self.term_forms = []
|
||||
|
||||
class PyConceptAdapter:
|
||||
''' RSForm adapter for interacting with pyconcept module. '''
|
||||
def __init__(self, instance: RSForm):
|
||||
|
@ -456,14 +507,14 @@ class PyConceptAdapter:
|
|||
|
||||
def _complete_rsform_details(self, data: dict) -> dict:
|
||||
result = deepcopy(data)
|
||||
result['id'] = self.schema.pk
|
||||
result['alias'] = self.schema.alias
|
||||
result['title'] = self.schema.title
|
||||
result['comment'] = self.schema.comment
|
||||
result['time_update'] = self.schema.time_update
|
||||
result['time_create'] = self.schema.time_create
|
||||
result['is_common'] = self.schema.is_common
|
||||
result['owner'] = (self.schema.owner.pk if self.schema.owner is not None else None)
|
||||
result['id'] = self.schema.item.pk
|
||||
result['alias'] = self.schema.item.alias
|
||||
result['title'] = self.schema.item.title
|
||||
result['comment'] = self.schema.item.comment
|
||||
result['time_update'] = self.schema.item.time_update
|
||||
result['time_create'] = self.schema.item.time_create
|
||||
result['is_common'] = self.schema.item.is_common
|
||||
result['owner'] = (self.schema.item.owner.pk if self.schema.item.owner is not None else None)
|
||||
for cst_data in result['items']:
|
||||
cst = Constituenta.objects.get(pk=cst_data['id'])
|
||||
cst_data['convention'] = cst.convention
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.db import transaction
|
|||
from cctext import Resolver, Reference, ReferenceType, EntityReference, SyntacticReference
|
||||
|
||||
from .utils import fix_old_references
|
||||
from .models import Constituenta, RSForm
|
||||
from .models import Constituenta, LibraryItem, RSForm
|
||||
|
||||
_CST_TYPE = 'constituenta'
|
||||
_TRS_TYPE = 'rsform'
|
||||
|
@ -30,13 +30,27 @@ class TextSerializer(serializers.Serializer):
|
|||
text = serializers.CharField()
|
||||
|
||||
|
||||
class RSFormMetaSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: General purpose RSForm data. '''
|
||||
class LibraryItemSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Library item data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = LibraryItem
|
||||
fields = '__all__'
|
||||
read_only_fields = ('owner', 'id', 'item_type')
|
||||
|
||||
|
||||
class RSFormSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Detailed data for RSForm. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = RSForm
|
||||
fields = '__all__'
|
||||
read_only_fields = ('owner', 'id')
|
||||
|
||||
def to_representation(self, instance: RSForm):
|
||||
result = LibraryItemSerializer(instance.item).data
|
||||
result['items'] = []
|
||||
for cst in instance.constituents().order_by('order'):
|
||||
result['items'].append(ConstituentaSerializer(cst).data)
|
||||
return result
|
||||
|
||||
|
||||
class RSFormUploadSerializer(serializers.Serializer):
|
||||
|
@ -45,26 +59,8 @@ class RSFormUploadSerializer(serializers.Serializer):
|
|||
load_metadata = serializers.BooleanField()
|
||||
|
||||
|
||||
class RSFormContentsSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Detailed data for RSForm. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = RSForm
|
||||
|
||||
def to_representation(self, instance: RSForm):
|
||||
result = RSFormMetaSerializer(instance).data
|
||||
result['items'] = []
|
||||
for cst in instance.constituents().order_by('order'):
|
||||
result['items'].append(ConstituentaSerializer(cst).data)
|
||||
return result
|
||||
|
||||
|
||||
class RSFormTRSSerializer(serializers.Serializer):
|
||||
''' Serializer: TRS file production and loading for RSForm. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = RSForm
|
||||
|
||||
def to_representation(self, instance: RSForm) -> dict:
|
||||
result = self._prepare_json_rsform(instance)
|
||||
items = instance.constituents().order_by('order')
|
||||
|
@ -76,9 +72,9 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
def _prepare_json_rsform(schema: RSForm) -> dict:
|
||||
return {
|
||||
'type': _TRS_TYPE,
|
||||
'title': schema.title,
|
||||
'alias': schema.alias,
|
||||
'comment': schema.comment,
|
||||
'title': schema.item.title,
|
||||
'alias': schema.item.alias,
|
||||
'comment': schema.item.comment,
|
||||
'items': [],
|
||||
'claimed': False,
|
||||
'selection': [],
|
||||
|
@ -114,6 +110,8 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
result['owner'] = data['owner']
|
||||
if 'is_common' in data:
|
||||
result['is_common'] = data['is_common']
|
||||
if 'is_canonical' in data:
|
||||
result['is_canonical'] = data['is_canonical']
|
||||
result['items'] = data.get('items', [])
|
||||
if self.context['load_meta']:
|
||||
result['title'] = data.get('title', 'Без названия')
|
||||
|
@ -121,7 +119,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(LibraryItem.objects.get(pk=result['id']))
|
||||
return result
|
||||
|
||||
def validate(self, attrs: dict):
|
||||
|
@ -135,19 +133,20 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data: dict) -> RSForm:
|
||||
self.instance = RSForm(
|
||||
self.instance: RSForm = RSForm.create(
|
||||
owner=validated_data.get('owner', None),
|
||||
alias=validated_data['alias'],
|
||||
title=validated_data['title'],
|
||||
comment=validated_data['comment'],
|
||||
is_common=validated_data['is_common']
|
||||
is_common=validated_data['is_common'],
|
||||
is_canonical=validated_data['is_canonical']
|
||||
)
|
||||
self.instance.save()
|
||||
self.instance.item.save()
|
||||
order = 1
|
||||
for cst_data in validated_data['items']:
|
||||
cst = Constituenta(
|
||||
alias=cst_data['alias'],
|
||||
schema=self.instance,
|
||||
schema=self.instance.item,
|
||||
order=order,
|
||||
cst_type=cst_data['cstType'],
|
||||
)
|
||||
|
@ -160,11 +159,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.item.alias = validated_data['alias']
|
||||
if 'title' in validated_data:
|
||||
instance.title = validated_data['title']
|
||||
instance.item.title = validated_data['title']
|
||||
if 'comment' in validated_data:
|
||||
instance.comment = validated_data['comment']
|
||||
instance.item.comment = validated_data['comment']
|
||||
|
||||
order = 1
|
||||
prev_constituents = instance.constituents()
|
||||
|
@ -181,7 +180,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
else:
|
||||
cst = Constituenta(
|
||||
alias=cst_data['alias'],
|
||||
schema=instance,
|
||||
schema=instance.item,
|
||||
order=order,
|
||||
cst_type=cst_data['cstType'],
|
||||
)
|
||||
|
@ -196,7 +195,7 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
|
||||
instance.update_order()
|
||||
instance.resolve_all_text()
|
||||
instance.save()
|
||||
instance.item.save()
|
||||
return instance
|
||||
|
||||
@staticmethod
|
||||
|
@ -225,7 +224,7 @@ class ConstituentaSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id', 'order', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
|
||||
|
||||
def update(self, instance: Constituenta, validated_data) -> Constituenta:
|
||||
schema: RSForm = instance.schema
|
||||
schema = RSForm(instance.schema)
|
||||
definition: Optional[str] = validated_data['definition_raw'] if 'definition_raw' in validated_data else None
|
||||
term: Optional[str] = validated_data['term_raw'] if 'term_raw' in validated_data else None
|
||||
term_changed = False
|
||||
|
@ -240,7 +239,7 @@ class ConstituentaSerializer(serializers.ModelSerializer):
|
|||
if term_changed:
|
||||
schema.on_term_change([result.alias])
|
||||
result.refresh_from_db()
|
||||
schema.save()
|
||||
schema.item.save()
|
||||
return result
|
||||
|
||||
|
||||
|
@ -281,16 +280,16 @@ class CstRenameSerializer(serializers.ModelSerializer):
|
|||
def validate(self, attrs):
|
||||
schema = cast(RSForm, self.context['schema'])
|
||||
old_cst = Constituenta.objects.get(pk=self.initial_data['id'])
|
||||
if old_cst.schema != schema:
|
||||
if old_cst.schema != schema.item:
|
||||
raise serializers.ValidationError({
|
||||
'id': f'Изменяемая конституента должна относиться к изменяемой схеме: {schema.title}'
|
||||
'id': f'Изменяемая конституента должна относиться к изменяемой схеме: {schema.item.title}'
|
||||
})
|
||||
if old_cst.alias == self.initial_data['alias']:
|
||||
raise serializers.ValidationError({
|
||||
'alias': f'Имя конституенты должно отличаться от текущего: {self.initial_data["alias"]}'
|
||||
})
|
||||
self.instance = old_cst
|
||||
attrs['schema'] = schema
|
||||
attrs['schema'] = schema.item
|
||||
attrs['id'] = self.initial_data['id']
|
||||
return attrs
|
||||
|
||||
|
@ -306,7 +305,7 @@ class CstListSerializer(serializers.Serializer):
|
|||
cstList = []
|
||||
for item in attrs['items']:
|
||||
cst = item['object']
|
||||
if cst.schema != schema:
|
||||
if cst.schema != schema.item:
|
||||
raise serializers.ValidationError(
|
||||
{'items': f'Конституенты должны относиться к данной схеме: {item}'})
|
||||
cstList.append(cst)
|
||||
|
|
|
@ -8,14 +8,17 @@ from apps.rsform.models import (
|
|||
RSForm,
|
||||
Constituenta,
|
||||
CstType,
|
||||
User
|
||||
User,
|
||||
LibraryItem,
|
||||
LibraryItemType
|
||||
)
|
||||
|
||||
|
||||
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 = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test1')
|
||||
self.schema2 = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test2')
|
||||
|
||||
def test_str(self):
|
||||
testStr = 'X1'
|
||||
|
@ -63,7 +66,8 @@ class TestConstituenta(TestCase):
|
|||
self.assertEqual(cst.definition_raw, '')
|
||||
|
||||
|
||||
class TestRSForm(TestCase):
|
||||
class TestLibraryItem(TestCase):
|
||||
''' Testing LibraryItem model. '''
|
||||
def setUp(self):
|
||||
self.user1 = User.objects.create(username='User1')
|
||||
self.user2 = User.objects.create(username='User2')
|
||||
|
@ -71,65 +75,79 @@ class TestRSForm(TestCase):
|
|||
|
||||
def test_str(self):
|
||||
testStr = 'Test123'
|
||||
schema = RSForm.objects.create(title=testStr, owner=self.user1, alias='КС1')
|
||||
self.assertEqual(str(schema), testStr)
|
||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM,
|
||||
title=testStr, owner=self.user1, alias='КС1')
|
||||
self.assertEqual(str(item), testStr)
|
||||
|
||||
def test_url(self):
|
||||
testStr = 'Test123'
|
||||
schema = RSForm.objects.create(title=testStr, owner=self.user1, alias='КС1')
|
||||
self.assertEqual(schema.get_absolute_url(), f'/api/rsforms/{schema.id}/')
|
||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM,
|
||||
title=testStr, owner=self.user1, alias='КС1')
|
||||
self.assertEqual(item.get_absolute_url(), f'/api/library/{item.id}/')
|
||||
|
||||
def test_create_default(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
self.assertIsNone(schema.owner)
|
||||
self.assertEqual(schema.title, 'Test')
|
||||
self.assertEqual(schema.alias, '')
|
||||
self.assertEqual(schema.comment, '')
|
||||
self.assertEqual(schema.is_common, False)
|
||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test')
|
||||
self.assertIsNone(item.owner)
|
||||
self.assertEqual(item.title, 'Test')
|
||||
self.assertEqual(item.alias, '')
|
||||
self.assertEqual(item.comment, '')
|
||||
self.assertEqual(item.is_common, False)
|
||||
self.assertEqual(item.is_canonical, False)
|
||||
|
||||
def test_create(self):
|
||||
schema = RSForm.objects.create(
|
||||
item = LibraryItem.objects.create(
|
||||
item_type=LibraryItemType.RSFORM,
|
||||
title='Test',
|
||||
owner=self.user1,
|
||||
alias='KS1',
|
||||
comment='Test comment',
|
||||
is_common=True
|
||||
is_common=True,
|
||||
is_canonical=True
|
||||
)
|
||||
self.assertEqual(schema.owner, self.user1)
|
||||
self.assertEqual(schema.title, 'Test')
|
||||
self.assertEqual(schema.alias, 'KS1')
|
||||
self.assertEqual(schema.comment, 'Test comment')
|
||||
self.assertEqual(schema.is_common, True)
|
||||
self.assertEqual(item.owner, self.user1)
|
||||
self.assertEqual(item.title, 'Test')
|
||||
self.assertEqual(item.alias, 'KS1')
|
||||
self.assertEqual(item.comment, 'Test comment')
|
||||
self.assertEqual(item.is_common, True)
|
||||
self.assertEqual(item.is_canonical, True)
|
||||
|
||||
|
||||
class TestRSForm(TestCase):
|
||||
''' Testing RSForm wrapper. '''
|
||||
def setUp(self):
|
||||
self.user1 = User.objects.create(username='User1')
|
||||
self.user2 = User.objects.create(username='User2')
|
||||
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.item, order=1)
|
||||
Constituenta.objects.create(alias='X2', schema=schema1.item, order=2)
|
||||
self.assertTrue(schema1.constituents().exists())
|
||||
self.assertFalse(schema2.constituents().exists())
|
||||
self.assertEqual(schema1.constituents().count(), 2)
|
||||
|
||||
def test_insert_at(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
cst1 = schema.insert_at(1, 'X1', CstType.BASE)
|
||||
self.assertEqual(cst1.order, 1)
|
||||
self.assertEqual(cst1.schema, schema)
|
||||
self.assertEqual(cst1.schema, schema.item)
|
||||
|
||||
cst2 = schema.insert_at(1, 'X2', CstType.BASE)
|
||||
cst1.refresh_from_db()
|
||||
self.assertEqual(cst2.order, 1)
|
||||
self.assertEqual(cst2.schema, schema)
|
||||
self.assertEqual(cst2.schema, schema.item)
|
||||
self.assertEqual(cst1.order, 2)
|
||||
|
||||
cst3 = schema.insert_at(4, 'X3', CstType.BASE)
|
||||
cst2.refresh_from_db()
|
||||
cst1.refresh_from_db()
|
||||
self.assertEqual(cst3.order, 3)
|
||||
self.assertEqual(cst3.schema, schema)
|
||||
self.assertEqual(cst3.schema, schema.item)
|
||||
self.assertEqual(cst2.order, 1)
|
||||
self.assertEqual(cst1.order, 2)
|
||||
|
||||
|
@ -138,7 +156,7 @@ class TestRSForm(TestCase):
|
|||
cst2.refresh_from_db()
|
||||
cst1.refresh_from_db()
|
||||
self.assertEqual(cst4.order, 3)
|
||||
self.assertEqual(cst4.schema, schema)
|
||||
self.assertEqual(cst4.schema, schema.item)
|
||||
self.assertEqual(cst3.order, 4)
|
||||
self.assertEqual(cst2.order, 1)
|
||||
self.assertEqual(cst1.order, 2)
|
||||
|
@ -147,7 +165,7 @@ class TestRSForm(TestCase):
|
|||
schema.insert_at(0, 'X5', CstType.BASE)
|
||||
|
||||
def test_insert_at_reorder(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
schema.insert_at(1, 'X1', CstType.BASE)
|
||||
d1 = schema.insert_at(2, 'D1', CstType.TERM)
|
||||
d2 = schema.insert_at(1, 'D2', CstType.TERM)
|
||||
|
@ -159,18 +177,18 @@ class TestRSForm(TestCase):
|
|||
self.assertEqual(x2.order, 2)
|
||||
|
||||
def test_insert_last(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
cst1 = schema.insert_last('X1', CstType.BASE)
|
||||
self.assertEqual(cst1.order, 1)
|
||||
self.assertEqual(cst1.schema, schema)
|
||||
self.assertEqual(cst1.schema, schema.item)
|
||||
|
||||
cst2 = schema.insert_last('X2', CstType.BASE)
|
||||
self.assertEqual(cst2.order, 2)
|
||||
self.assertEqual(cst2.schema, schema)
|
||||
self.assertEqual(cst2.schema, schema.item)
|
||||
self.assertEqual(cst1.order, 1)
|
||||
|
||||
def test_create_cst_resolve(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
cst1 = schema.insert_last('X1', CstType.BASE)
|
||||
cst1.term_raw = '@{X2|datv}'
|
||||
cst1.definition_raw = '@{X1|datv} @{X2|datv}'
|
||||
|
@ -188,7 +206,7 @@ class TestRSForm(TestCase):
|
|||
self.assertEqual(cst2.definition_resolved, 'слонам слоны')
|
||||
|
||||
def test_delete_cst(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
x1 = schema.insert_last('X1', CstType.BASE)
|
||||
x2 = schema.insert_last('X2', CstType.BASE)
|
||||
d1 = schema.insert_last('D1', CstType.TERM)
|
||||
|
@ -196,13 +214,13 @@ class TestRSForm(TestCase):
|
|||
schema.delete_cst([x2, d1])
|
||||
x1.refresh_from_db()
|
||||
d2.refresh_from_db()
|
||||
schema.refresh_from_db()
|
||||
schema.item.refresh_from_db()
|
||||
self.assertEqual(schema.constituents().count(), 2)
|
||||
self.assertEqual(x1.order, 1)
|
||||
self.assertEqual(d2.order, 2)
|
||||
|
||||
def test_move_cst(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
x1 = schema.insert_last('X1', CstType.BASE)
|
||||
x2 = schema.insert_last('X2', CstType.BASE)
|
||||
d1 = schema.insert_last('D1', CstType.TERM)
|
||||
|
@ -218,7 +236,7 @@ class TestRSForm(TestCase):
|
|||
self.assertEqual(d2.order, 3)
|
||||
|
||||
def test_move_cst_down(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
x1 = schema.insert_last('X1', CstType.BASE)
|
||||
x2 = schema.insert_last('X2', CstType.BASE)
|
||||
schema.move_cst([x1], 2)
|
||||
|
@ -228,7 +246,7 @@ class TestRSForm(TestCase):
|
|||
self.assertEqual(x2.order, 1)
|
||||
|
||||
def test_reset_aliases(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
x1 = schema.insert_last('X11', CstType.BASE)
|
||||
x2 = schema.insert_last('X21', CstType.BASE)
|
||||
d1 = schema.insert_last('D11', CstType.TERM)
|
||||
|
|
|
@ -9,7 +9,7 @@ from rest_framework.exceptions import ErrorDetail
|
|||
from cctext import ReferenceType
|
||||
|
||||
from apps.users.models import User
|
||||
from apps.rsform.models import Syntax, RSForm, Constituenta, CstType
|
||||
from apps.rsform.models import Syntax, RSForm, Constituenta, CstType, LibraryItem, LibraryItemType
|
||||
from apps.rsform.views import (
|
||||
convert_to_ascii,
|
||||
convert_to_math,
|
||||
|
@ -17,28 +17,28 @@ from apps.rsform.views import (
|
|||
)
|
||||
|
||||
|
||||
def _response_contains(response, schema: RSForm) -> bool:
|
||||
return any(x for x in response.data if x['id'] == schema.pk)
|
||||
def _response_contains(response, item: LibraryItem) -> bool:
|
||||
return any(x for x in response.data if x['id'] == item.pk)
|
||||
|
||||
|
||||
class TestConstituentaAPI(APITestCase):
|
||||
''' Testing constituenta view. '''
|
||||
''' Testing Constituenta view. '''
|
||||
def setUp(self):
|
||||
self.factory = APIRequestFactory()
|
||||
self.user = User.objects.create(username='UserTest')
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
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', schema=self.rsform_owned, order=1, convention='Test',
|
||||
alias='X1', schema=self.rsform_owned.item, order=1, convention='Test',
|
||||
term_raw='Test1', term_resolved='Test1R',
|
||||
term_forms=[{'text':'form1', 'tags':'sing,datv'}])
|
||||
self.cst2 = Constituenta.objects.create(
|
||||
alias='X2', schema=self.rsform_unowned, order=1, convention='Test1',
|
||||
alias='X2', schema=self.rsform_unowned.item, order=1, convention='Test1',
|
||||
term_raw='Test2', term_resolved='Test2R')
|
||||
self.cst3 = Constituenta.objects.create(
|
||||
alias='X3', schema=self.rsform_owned, order=2,
|
||||
alias='X3', schema=self.rsform_owned.item, order=2,
|
||||
term_raw='Test3', term_resolved='Test3',
|
||||
definition_raw='Test1', definition_resolved='Test2')
|
||||
|
||||
|
@ -102,6 +102,110 @@ class TestConstituentaAPI(APITestCase):
|
|||
self.assertEqual(response.data['order'], self.cst1.order)
|
||||
|
||||
|
||||
class TestLibraryViewset(APITestCase):
|
||||
''' Testing Library view. '''
|
||||
def setUp(self):
|
||||
self.factory = APIRequestFactory()
|
||||
self.user = User.objects.create(username='UserTest')
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
self.owned = LibraryItem.objects.create(
|
||||
item_type=LibraryItemType.RSFORM,
|
||||
title='Test',
|
||||
alias='T1',
|
||||
owner=self.user
|
||||
)
|
||||
self.unowned = LibraryItem.objects.create(
|
||||
item_type=LibraryItemType.RSFORM,
|
||||
title='Test2',
|
||||
alias='T2'
|
||||
)
|
||||
self.common = LibraryItem.objects.create(
|
||||
item_type=LibraryItemType.RSFORM,
|
||||
title='Test3',
|
||||
alias='T3',
|
||||
is_common=True
|
||||
)
|
||||
|
||||
def test_create_anonymous(self):
|
||||
self.client.logout()
|
||||
data = json.dumps({'title': 'Title'})
|
||||
response = self.client.post('/api/library/', data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_create_populate_user(self):
|
||||
data = json.dumps({'title': 'Title'})
|
||||
response = self.client.post('/api/library/', data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['title'], 'Title')
|
||||
self.assertEqual(response.data['owner'], self.user.id)
|
||||
|
||||
def test_update(self):
|
||||
data = json.dumps({'id': self.owned.id, 'title': 'New title'})
|
||||
response = self.client.patch(f'/api/library/{self.owned.id}/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['title'], 'New title')
|
||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
||||
|
||||
def test_update_unowned(self):
|
||||
data = json.dumps({'id': self.unowned.id, 'title': 'New title'})
|
||||
response = self.client.patch(f'/api/library/{self.unowned.id}/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_destroy(self):
|
||||
response = self.client.delete(f'/api/library/{self.owned.id}/')
|
||||
self.assertTrue(response.status_code in [202, 204])
|
||||
|
||||
def test_destroy_admin_override(self):
|
||||
response = self.client.delete(f'/api/library/{self.unowned.id}/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
response = self.client.delete(f'/api/library/{self.unowned.id}/')
|
||||
self.assertTrue(response.status_code in [202, 204])
|
||||
|
||||
def test_claim(self):
|
||||
response = self.client.post(f'/api/library/{self.owned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
self.owned.is_common = True
|
||||
self.owned.save()
|
||||
response = self.client.post(f'/api/library/{self.owned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 304)
|
||||
|
||||
response = self.client.post(f'/api/library/{self.unowned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
self.unowned.is_common = True
|
||||
self.unowned.save()
|
||||
response = self.client.post(f'/api/library/{self.unowned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.unowned.refresh_from_db()
|
||||
self.assertEqual(self.unowned.owner, self.user)
|
||||
|
||||
def test_claim_anonymous(self):
|
||||
self.client.logout()
|
||||
response = self.client.post(f'/api/library/{self.owned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_retrieve_common(self):
|
||||
self.client.logout()
|
||||
response = self.client.get('/api/library/active/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(_response_contains(response, self.common))
|
||||
self.assertFalse(_response_contains(response, self.unowned))
|
||||
self.assertFalse(_response_contains(response, self.owned))
|
||||
|
||||
def test_retrieve_owned(self):
|
||||
response = self.client.get('/api/library/active/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(_response_contains(response, self.common))
|
||||
self.assertFalse(_response_contains(response, self.unowned))
|
||||
self.assertTrue(_response_contains(response, self.owned))
|
||||
|
||||
|
||||
class TestRSFormViewset(APITestCase):
|
||||
''' Testing RSForm view. '''
|
||||
def setUp(self):
|
||||
|
@ -109,56 +213,34 @@ class TestRSFormViewset(APITestCase):
|
|||
self.user = User.objects.create(username='UserTest')
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
self.rsform_owned = RSForm.objects.create(title='Test', alias='T1', owner=self.user)
|
||||
self.rsform_unowned = RSForm.objects.create(title='Test2', alias='T2')
|
||||
self.owned = RSForm.create(title='Test', alias='T1', owner=self.user)
|
||||
self.unowned = RSForm.create(title='Test2', alias='T2')
|
||||
|
||||
def test_create_anonymous(self):
|
||||
self.client.logout()
|
||||
data = json.dumps({'title': 'Title'})
|
||||
response = self.client.post('/api/rsforms/', data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_create_populate_user(self):
|
||||
data = json.dumps({'title': 'Title'})
|
||||
response = self.client.post('/api/rsforms/', data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['title'], 'Title')
|
||||
self.assertEqual(response.data['owner'], self.user.id)
|
||||
|
||||
def test_update(self):
|
||||
data = json.dumps({'id': self.rsform_owned.id, 'title': 'New title'})
|
||||
response = self.client.patch(f'/api/rsforms/{self.rsform_owned.id}/',
|
||||
data=data, content_type='application/json')
|
||||
def test_list(self):
|
||||
non_schema = LibraryItem.objects.create(
|
||||
item_type=LibraryItemType.OPERATIONS_SCHEMA,
|
||||
title='Test3'
|
||||
)
|
||||
response = self.client.get('/api/rsforms/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['title'], 'New title')
|
||||
self.assertEqual(response.data['alias'], self.rsform_owned.alias)
|
||||
self.assertFalse(_response_contains(response, non_schema))
|
||||
self.assertTrue(_response_contains(response, self.unowned.item))
|
||||
self.assertTrue(_response_contains(response, self.owned.item))
|
||||
|
||||
def test_update_unowned(self):
|
||||
data = json.dumps({'id': self.rsform_unowned.id, 'title': 'New title'})
|
||||
response = self.client.patch(f'/api/rsforms/{self.rsform_unowned.id}/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_destroy(self):
|
||||
response = self.client.delete(f'/api/rsforms/{self.rsform_owned.id}/')
|
||||
self.assertTrue(response.status_code in [202, 204])
|
||||
|
||||
def test_destroy_admin_override(self):
|
||||
response = self.client.delete(f'/api/rsforms/{self.rsform_unowned.id}/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
response = self.client.delete(f'/api/rsforms/{self.rsform_unowned.id}/')
|
||||
self.assertTrue(response.status_code in [202, 204])
|
||||
response = self.client.get('/api/library/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(_response_contains(response, non_schema))
|
||||
self.assertTrue(_response_contains(response, self.unowned.item))
|
||||
self.assertTrue(_response_contains(response, self.owned.item))
|
||||
|
||||
def test_contents(self):
|
||||
schema = RSForm.objects.create(title='Title1')
|
||||
schema = RSForm.create(title='Title1')
|
||||
schema.insert_last(alias='X1', insert_type=CstType.BASE)
|
||||
response = self.client.get(f'/api/rsforms/{schema.id}/contents/')
|
||||
response = self.client.get(f'/api/rsforms/{schema.item.id}/contents/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_details(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
x1 = schema.insert_at(1, 'X1', CstType.BASE)
|
||||
x2 = schema.insert_at(2, 'X2', CstType.BASE)
|
||||
x1.term_raw = 'человек'
|
||||
|
@ -168,7 +250,7 @@ class TestRSFormViewset(APITestCase):
|
|||
x1.save()
|
||||
x2.save()
|
||||
|
||||
response = self.client.get(f'/api/rsforms/{schema.id}/details/')
|
||||
response = self.client.get(f'/api/rsforms/{schema.item.id}/details/')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['title'], 'Test')
|
||||
|
@ -182,10 +264,10 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(response.data['items'][1]['term']['resolved'], x2.term_resolved)
|
||||
|
||||
def test_check(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
schema.insert_at(1, 'X1', CstType.BASE)
|
||||
data = json.dumps({'expression': 'X1=X1'})
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/check/', data=data, content_type='application/json')
|
||||
response = self.client.post(f'/api/rsforms/{schema.item.id}/check/', data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['parseResult'], True)
|
||||
self.assertEqual(response.data['syntax'], Syntax.MATH)
|
||||
|
@ -194,12 +276,12 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(response.data['valueClass'], 'value')
|
||||
|
||||
def test_resolve(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
x1 = schema.insert_at(1, 'X1', CstType.BASE)
|
||||
x1.term_resolved = 'синий слон'
|
||||
x1.save()
|
||||
data = json.dumps({'text': '@{1|редкий} @{X1|plur,datv}'})
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/resolve/', data=data, content_type='application/json')
|
||||
response = self.client.post(f'/api/rsforms/{schema.item.id}/resolve/', data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['input'], '@{1|редкий} @{X1|plur,datv}')
|
||||
self.assertEqual(response.data['output'], 'редким синим слонам')
|
||||
|
@ -231,9 +313,9 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertTrue(response.data['title'] != '')
|
||||
|
||||
def test_export_trs(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema = RSForm.create(title='Test')
|
||||
schema.insert_at(1, 'X1', CstType.BASE)
|
||||
response = self.client.get(f'/api/rsforms/{schema.id}/export-trs/')
|
||||
response = self.client.get(f'/api/rsforms/{schema.item.id}/export-trs/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.headers['Content-Disposition'], 'attachment; filename=Schema.trs')
|
||||
with io.BytesIO(response.content) as stream:
|
||||
|
@ -241,29 +323,16 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertIsNone(zipped_file.testzip())
|
||||
self.assertIn('document.json', zipped_file.namelist())
|
||||
|
||||
def test_claim(self):
|
||||
response = self.client.post(f'/api/rsforms/{self.rsform_owned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 304)
|
||||
response = self.client.post(f'/api/rsforms/{self.rsform_unowned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.rsform_unowned.refresh_from_db()
|
||||
self.assertEqual(self.rsform_unowned.owner, self.user)
|
||||
|
||||
def test_claim_anonymous(self):
|
||||
self.client.logout()
|
||||
response = self.client.post(f'/api/rsforms/{self.rsform_owned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_create_constituenta(self):
|
||||
data = json.dumps({'alias': 'X3', 'cst_type': 'basic'})
|
||||
response = self.client.post(f'/api/rsforms/{self.rsform_unowned.id}/cst-create/',
|
||||
response = self.client.post(f'/api/rsforms/{self.unowned.item.id}/cst-create/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
schema = self.rsform_owned
|
||||
Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
||||
item = self.owned.item
|
||||
Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=2)
|
||||
response = self.client.post(f'/api/rsforms/{item.id}/cst-create/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['new_cst']['alias'], 'X3')
|
||||
|
@ -271,7 +340,7 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(x3.order, 3)
|
||||
|
||||
data = json.dumps({'alias': 'X4', 'cst_type': 'basic', 'insert_after': x2.id})
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
||||
response = self.client.post(f'/api/rsforms/{item.id}/cst-create/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['new_cst']['alias'], 'X4')
|
||||
|
@ -280,34 +349,34 @@ class TestRSFormViewset(APITestCase):
|
|||
|
||||
def test_rename_constituenta(self):
|
||||
self.cst1 = Constituenta.objects.create(
|
||||
alias='X1', schema=self.rsform_owned, order=1, convention='Test',
|
||||
alias='X1', schema=self.owned.item, order=1, convention='Test',
|
||||
term_raw='Test1', term_resolved='Test1',
|
||||
term_forms=[{'text':'form1', 'tags':'sing,datv'}])
|
||||
self.cst2 = Constituenta.objects.create(
|
||||
alias='X2', schema=self.rsform_unowned, order=1, convention='Test1',
|
||||
alias='X2', schema=self.unowned.item, order=1, convention='Test1',
|
||||
term_raw='Test2', term_resolved='Test2')
|
||||
self.cst3 = Constituenta.objects.create(
|
||||
alias='X3', schema=self.rsform_owned, order=2,
|
||||
alias='X3', schema=self.owned.item, order=2,
|
||||
term_raw='Test3', term_resolved='Test3',
|
||||
definition_raw='Test1', definition_resolved='Test2')
|
||||
|
||||
data = json.dumps({'alias': 'D2', 'cst_type': 'term', 'id': self.cst2.pk})
|
||||
response = self.client.patch(f'/api/rsforms/{self.rsform_unowned.id}/cst-rename/',
|
||||
response = self.client.patch(f'/api/rsforms/{self.unowned.item.id}/cst-rename/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
response = self.client.patch(f'/api/rsforms/{self.rsform_owned.id}/cst-rename/',
|
||||
response = self.client.patch(f'/api/rsforms/{self.owned.item.id}/cst-rename/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
data = json.dumps({'alias': self.cst1.alias, 'cst_type': 'term', 'id': self.cst1.pk})
|
||||
response = self.client.patch(f'/api/rsforms/{self.rsform_owned.id}/cst-rename/',
|
||||
response = self.client.patch(f'/api/rsforms/{self.owned.item.id}/cst-rename/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
data = json.dumps({'alias': 'D2', 'cst_type': 'term', 'id': self.cst1.pk})
|
||||
schema = self.rsform_owned
|
||||
d1 = Constituenta.objects.create(schema=schema, alias='D1', cst_type='term', order=4)
|
||||
item = self.owned.item
|
||||
d1 = Constituenta.objects.create(schema=item, alias='D1', cst_type='term', order=4)
|
||||
d1.term_raw = '@{X1|plur}'
|
||||
d1.definition_formal = 'X1'
|
||||
d1.save()
|
||||
|
@ -315,7 +384,7 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(self.cst1.order, 1)
|
||||
self.assertEqual(self.cst1.alias, 'X1')
|
||||
self.assertEqual(self.cst1.cst_type, CstType.BASE)
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-rename/',
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-rename/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['new_cst']['alias'], 'D2')
|
||||
|
@ -337,8 +406,8 @@ class TestRSFormViewset(APITestCase):
|
|||
'definition_formal': '3',
|
||||
'definition_raw': '4'
|
||||
})
|
||||
schema = self.rsform_owned
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
||||
item = self.owned.item
|
||||
response = self.client.post(f'/api/rsforms/{item.id}/cst-create/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['new_cst']['alias'], 'X3')
|
||||
|
@ -351,66 +420,66 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(response.data['new_cst']['definition_resolved'], '4')
|
||||
|
||||
def test_delete_constituenta(self):
|
||||
schema = self.rsform_owned
|
||||
schema = self.owned
|
||||
data = json.dumps({'items': [{'id': 1337}]})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||
x1 = Constituenta.objects.create(schema=schema.item, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema.item, alias='X2', cst_type='basic', order=2)
|
||||
data = json.dumps({'items': [{'id': x1.id}]})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete/',
|
||||
data=data, content_type='application/json')
|
||||
x2.refresh_from_db()
|
||||
schema.refresh_from_db()
|
||||
schema.item.refresh_from_db()
|
||||
self.assertEqual(response.status_code, 202)
|
||||
self.assertEqual(len(response.data['items']), 1)
|
||||
self.assertEqual(schema.constituents().count(), 1)
|
||||
self.assertEqual(x2.alias, 'X2')
|
||||
self.assertEqual(x2.order, 1)
|
||||
|
||||
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', cst_type='basic', order=1)
|
||||
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1)
|
||||
data = json.dumps({'items': [{'id': x3.id}]})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_move_constituenta(self):
|
||||
schema = self.rsform_owned
|
||||
item = self.owned.item
|
||||
data = json.dumps({'items': [{'id': 1337}], 'move_to': 1})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||
x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=2)
|
||||
data = json.dumps({'items': [{'id': x2.id}], 'move_to': 1})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto/',
|
||||
data=data, content_type='application/json')
|
||||
x1.refresh_from_db()
|
||||
x2.refresh_from_db()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['id'], schema.id)
|
||||
self.assertEqual(response.data['id'], item.id)
|
||||
self.assertEqual(x1.order, 2)
|
||||
self.assertEqual(x2.order, 1)
|
||||
|
||||
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', cst_type='basic', order=1)
|
||||
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1)
|
||||
data = json.dumps({'items': [{'id': x3.id}], 'move_to': 1})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_reset_aliases(self):
|
||||
schema = self.rsform_owned
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/reset-aliases/')
|
||||
item = self.owned.item
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/reset-aliases/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['id'], schema.id)
|
||||
self.assertEqual(response.data['id'], item.id)
|
||||
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=1)
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=2)
|
||||
d11 = Constituenta.objects.create(schema=schema, alias='D11', cst_type='term', order=3)
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/reset-aliases/')
|
||||
x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=1)
|
||||
x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=2)
|
||||
d11 = Constituenta.objects.create(schema=item, alias='D11', cst_type='term', order=3)
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/reset-aliases/')
|
||||
x1.refresh_from_db()
|
||||
x2.refresh_from_db()
|
||||
d11.refresh_from_db()
|
||||
|
@ -422,31 +491,31 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(d11.order, 3)
|
||||
self.assertEqual(d11.alias, 'D1')
|
||||
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/reset-aliases/')
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/reset-aliases/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_load_trs(self):
|
||||
schema = self.rsform_owned
|
||||
schema.title = 'Testt11'
|
||||
schema.save()
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||
schema = self.owned
|
||||
schema.item.title = 'Testt11'
|
||||
schema.item.save()
|
||||
x1 = Constituenta.objects.create(schema=schema.item, alias='X1', cst_type='basic', order=1)
|
||||
work_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file:
|
||||
data = {'file': file, 'load_metadata': False}
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/load-trs/', data=data, format='multipart')
|
||||
schema.refresh_from_db()
|
||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/load-trs/', data=data, format='multipart')
|
||||
schema.item.refresh_from_db()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(schema.title, 'Testt11')
|
||||
self.assertEqual(schema.item.title, 'Testt11')
|
||||
self.assertEqual(len(response.data['items']), 25)
|
||||
self.assertEqual(schema.constituents().count(), 25)
|
||||
self.assertFalse(Constituenta.objects.all().filter(pk=x1.id).exists())
|
||||
|
||||
def test_clone(self):
|
||||
schema = self.rsform_owned
|
||||
schema.title = 'Testt11'
|
||||
schema.save()
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X12', cst_type='basic', order=1)
|
||||
d1 = Constituenta.objects.create(schema=schema, alias='D2', cst_type='term', order=1)
|
||||
item = self.owned.item
|
||||
item.title = 'Testt11'
|
||||
item.save()
|
||||
x1 = Constituenta.objects.create(schema=item, alias='X12', cst_type='basic', order=1)
|
||||
d1 = Constituenta.objects.create(schema=item, alias='D2', cst_type='term', order=1)
|
||||
x1.term_raw = 'человек'
|
||||
x1.term_resolved = 'человек'
|
||||
d1.term_raw = '@{X12|plur}'
|
||||
|
@ -455,7 +524,7 @@ class TestRSFormViewset(APITestCase):
|
|||
d1.save()
|
||||
|
||||
data = json.dumps({'title': 'Title'})
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/clone/', data=data, content_type='application/json')
|
||||
response = self.client.post(f'/api/library/{item.id}/clone/', data=data, content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['title'], 'Title')
|
||||
|
@ -536,29 +605,3 @@ class TestFunctionalViews(APITestCase):
|
|||
response = parse_expression(request)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIsInstance(response.data['expression'][0], ErrorDetail)
|
||||
|
||||
|
||||
class TestLibraryAPI(APITestCase):
|
||||
def setUp(self):
|
||||
self.factory = APIRequestFactory()
|
||||
self.user = User.objects.create(username='UserTest')
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
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_common = RSForm.objects.create(title='Test3', alias='T3', is_common=True)
|
||||
|
||||
def test_retrieve_common(self):
|
||||
self.client.logout()
|
||||
response = self.client.get('/api/library/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(_response_contains(response, self.rsform_common))
|
||||
self.assertFalse(_response_contains(response, self.rsform_unowned))
|
||||
self.assertFalse(_response_contains(response, self.rsform_owned))
|
||||
|
||||
def test_retrieve_owned(self):
|
||||
response = self.client.get('/api/library/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(_response_contains(response, self.rsform_common))
|
||||
self.assertFalse(_response_contains(response, self.rsform_unowned))
|
||||
self.assertTrue(_response_contains(response, self.rsform_owned))
|
||||
|
|
|
@ -3,16 +3,17 @@ from django.urls import path, include
|
|||
from rest_framework import routers
|
||||
from . import views
|
||||
|
||||
rsform_router = routers.SimpleRouter()
|
||||
rsform_router.register(r'rsforms', views.RSFormViewSet)
|
||||
library_router = routers.SimpleRouter()
|
||||
library_router.register('library', views.LibraryViewSet)
|
||||
library_router.register('rsforms', views.RSFormViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('library/', views.LibraryView.as_view(), name='library'),
|
||||
path('library/active/', views.LibraryActiveView.as_view(), name='library'),
|
||||
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('func/parse-expression/', views.parse_expression),
|
||||
path('func/to-ascii/', views.convert_to_ascii),
|
||||
path('func/to-math/', views.convert_to_math),
|
||||
path('', include(rsform_router.urls)),
|
||||
path('', include(library_router.urls)),
|
||||
]
|
||||
|
|
|
@ -3,7 +3,7 @@ import json
|
|||
from io import BytesIO
|
||||
import re
|
||||
from zipfile import ZipFile
|
||||
from rest_framework.permissions import BasePermission
|
||||
from rest_framework.permissions import BasePermission, IsAuthenticated
|
||||
|
||||
|
||||
class ObjectOwnerOrAdmin(BasePermission):
|
||||
|
@ -16,6 +16,14 @@ class ObjectOwnerOrAdmin(BasePermission):
|
|||
return request.user.is_staff # type: ignore
|
||||
|
||||
|
||||
class IsClaimable(IsAuthenticated):
|
||||
''' Permission for object ownership restriction '''
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if not super().has_permission(request, view):
|
||||
return False
|
||||
return obj.is_common
|
||||
|
||||
|
||||
class SchemaOwnerOrAdmin(BasePermission):
|
||||
''' Permission for object ownership restriction '''
|
||||
def has_object_permission(self, request, view, obj):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
''' REST API: RSForms for conceptual schemas. '''
|
||||
import json
|
||||
from typing import cast
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponse
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
@ -10,28 +11,28 @@ from rest_framework.response import Response
|
|||
from rest_framework.decorators import api_view
|
||||
|
||||
import pyconcept
|
||||
from . import models
|
||||
from . import serializers
|
||||
from . import models as m
|
||||
from . import serializers as s
|
||||
from . import utils
|
||||
|
||||
|
||||
class LibraryView(generics.ListAPIView):
|
||||
class LibraryActiveView(generics.ListAPIView):
|
||||
''' Endpoint: Get list of rsforms available for active user. '''
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.RSFormMetaSerializer
|
||||
serializer_class = s.LibraryItemSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if not user.is_anonymous:
|
||||
return models.RSForm.objects.filter(Q(is_common=True) | Q(owner=user))
|
||||
return m.LibraryItem.objects.filter(Q(is_common=True) | Q(owner=user))
|
||||
else:
|
||||
return models.RSForm.objects.filter(is_common=True)
|
||||
return m.LibraryItem.objects.filter(is_common=True)
|
||||
|
||||
|
||||
class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
||||
''' Endpoint: Get / Update Constituenta. '''
|
||||
queryset = models.Constituenta.objects.all()
|
||||
serializer_class = serializers.ConstituentaSerializer
|
||||
queryset = m.Constituenta.objects.all()
|
||||
serializer_class = s.ConstituentaSerializer
|
||||
|
||||
def get_permissions(self):
|
||||
result = super().get_permissions()
|
||||
|
@ -41,21 +42,17 @@ class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
|||
result.append(utils.SchemaOwnerOrAdmin())
|
||||
return result
|
||||
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
class RSFormViewSet(viewsets.ModelViewSet):
|
||||
''' Endpoint: RSForm operations. '''
|
||||
queryset = models.RSForm.objects.all()
|
||||
serializer_class = serializers.RSFormMetaSerializer
|
||||
class LibraryViewSet(viewsets.ModelViewSet):
|
||||
''' Endpoint: Library operations. '''
|
||||
queryset = m.LibraryItem.objects.all()
|
||||
serializer_class = s.LibraryItemSerializer
|
||||
|
||||
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
|
||||
filterset_fields = ['owner', 'is_common']
|
||||
ordering_fields = ('owner', 'title', 'time_update')
|
||||
filterset_fields = ['item_type', 'owner', 'is_common', 'is_canonical']
|
||||
ordering_fields = ('item_type', 'owner', 'title', 'time_update')
|
||||
ordering = '-time_update'
|
||||
|
||||
def _get_schema(self) -> models.RSForm:
|
||||
return self.get_object() # type: ignore
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if not self.request.user.is_anonymous and 'owner' not in self.request.POST:
|
||||
return serializer.save(owner=self.request.user)
|
||||
|
@ -63,11 +60,64 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
return serializer.save()
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action in ['update', 'destroy', 'partial_update', 'load_trs',
|
||||
'cst_create', 'cst_multidelete', 'reset_aliases', 'cst_rename']:
|
||||
if self.action in ['update', 'destroy', 'partial_update']:
|
||||
permission_classes = [utils.ObjectOwnerOrAdmin]
|
||||
elif self.action in ['create', 'claim', 'clone']:
|
||||
elif self.action in ['create', 'clone']:
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
elif self.action in ['claim']:
|
||||
permission_classes = [utils.IsClaimable]
|
||||
else:
|
||||
permission_classes = [permissions.AllowAny]
|
||||
return [permission() for permission in permission_classes]
|
||||
|
||||
@transaction.atomic
|
||||
@action(detail=True, methods=['post'], url_path='clone')
|
||||
def clone(self, request, pk):
|
||||
''' Endpoint: Create deep copy of library item. '''
|
||||
serializer = s.LibraryItemSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
item = cast(m.LibraryItem, self.get_object())
|
||||
if item.item_type == m.LibraryItemType.RSFORM:
|
||||
schema = m.RSForm(item)
|
||||
clone_data = s.RSFormTRSSerializer(schema).data
|
||||
clone_data['item_type'] = item.item_type
|
||||
clone_data['owner'] = self.request.user
|
||||
clone_data['title'] = serializer.validated_data['title']
|
||||
clone_data['alias'] = serializer.validated_data.get('alias', '')
|
||||
clone_data['comment'] = serializer.validated_data.get('comment', '')
|
||||
clone_data['is_common'] = serializer.validated_data.get('is_common', False)
|
||||
clone_data['is_canonical'] = serializer.validated_data.get('is_canonical', False)
|
||||
clone = s.RSFormTRSSerializer(data=clone_data, context={'load_meta': True})
|
||||
clone.is_valid(raise_exception=True)
|
||||
new_schema = clone.save()
|
||||
return Response(status=201, data=m.PyConceptAdapter(new_schema).full())
|
||||
return Response(status=404)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def claim(self, request, pk=None):
|
||||
''' Endpoint: Claim ownership of LibraryItem. '''
|
||||
item = cast(m.LibraryItem, self.get_object())
|
||||
if item.owner == self.request.user:
|
||||
return Response(status=304)
|
||||
else:
|
||||
item.owner = self.request.user
|
||||
item.save()
|
||||
return Response(status=200, data=s.LibraryItemSerializer(item).data)
|
||||
|
||||
|
||||
class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
||||
''' Endpoint: RSForm operations. '''
|
||||
queryset = m.LibraryItem.objects.all().filter(item_type=m.LibraryItemType.RSFORM)
|
||||
serializer_class = s.LibraryItemSerializer
|
||||
|
||||
def _get_schema(self) -> m.RSForm:
|
||||
return m.RSForm(self.get_object()) # type: ignore
|
||||
|
||||
def get_permissions(self):
|
||||
''' Determine permission class. '''
|
||||
if self.action in ['load_trs', 'cst_create', 'cst_multidelete',
|
||||
'reset_aliases', 'cst_rename']:
|
||||
permission_classes = [utils.ObjectOwnerOrAdmin]
|
||||
else:
|
||||
permission_classes = [permissions.AllowAny]
|
||||
return [permission() for permission in permission_classes]
|
||||
|
@ -76,14 +126,14 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
def cst_create(self, request, pk):
|
||||
''' Create new constituenta. '''
|
||||
schema = self._get_schema()
|
||||
serializer = serializers.CstCreateSerializer(data=request.data)
|
||||
serializer = s.CstCreateSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
data = serializer.validated_data
|
||||
new_cst = schema.create_cst(data, data['insert_after'] if 'insert_after' in data else None)
|
||||
schema.refresh_from_db()
|
||||
schema.item.refresh_from_db()
|
||||
response = Response(status=201, data={
|
||||
'new_cst': serializers.ConstituentaSerializer(new_cst).data,
|
||||
'schema': models.PyConceptAdapter(schema).full()
|
||||
'new_cst': s.ConstituentaSerializer(new_cst).data,
|
||||
'schema': m.PyConceptAdapter(schema).full()
|
||||
})
|
||||
response['Location'] = new_cst.get_absolute_url()
|
||||
return response
|
||||
|
@ -93,109 +143,80 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
def cst_rename(self, request, pk):
|
||||
''' Rename constituenta possibly changing type. '''
|
||||
schema = self._get_schema()
|
||||
serializer = serializers.CstRenameSerializer(data=request.data, context={'schema': schema})
|
||||
serializer = s.CstRenameSerializer(data=request.data, context={'schema': schema})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
old_alias = models.Constituenta.objects.get(pk=request.data['id']).alias
|
||||
old_alias = m.Constituenta.objects.get(pk=request.data['id']).alias
|
||||
serializer.save()
|
||||
mapping = { old_alias: serializer.validated_data['alias'] }
|
||||
schema.apply_mapping(mapping, change_aliases=False)
|
||||
schema.update_order()
|
||||
schema.refresh_from_db()
|
||||
cst = models.Constituenta.objects.get(pk=serializer.validated_data['id'])
|
||||
schema.item.refresh_from_db()
|
||||
cst = m.Constituenta.objects.get(pk=serializer.validated_data['id'])
|
||||
return Response(status=200, data={
|
||||
'new_cst': serializers.ConstituentaSerializer(cst).data,
|
||||
'schema': models.PyConceptAdapter(schema).full()
|
||||
'new_cst': s.ConstituentaSerializer(cst).data,
|
||||
'schema': m.PyConceptAdapter(schema).full()
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['patch'], url_path='cst-multidelete')
|
||||
def cst_multidelete(self, request, pk):
|
||||
''' Endpoint: Delete multiple constituents. '''
|
||||
schema = self._get_schema()
|
||||
serializer = serializers.CstListSerializer(data=request.data, context={'schema': schema})
|
||||
serializer = s.CstListSerializer(data=request.data, context={'schema': schema})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema.delete_cst(serializer.validated_data['constituents'])
|
||||
schema.refresh_from_db()
|
||||
return Response(status=202, data=models.PyConceptAdapter(schema).full())
|
||||
schema.item.refresh_from_db()
|
||||
return Response(status=202, data=m.PyConceptAdapter(schema).full())
|
||||
|
||||
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
||||
def cst_moveto(self, request, pk):
|
||||
''' Endpoint: Move multiple constituents. '''
|
||||
schema = self._get_schema()
|
||||
serializer = serializers.CstMoveSerializer(data=request.data, context={'schema': schema})
|
||||
serializer = s.CstMoveSerializer(data=request.data, context={'schema': schema})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema.move_cst(serializer.validated_data['constituents'], serializer.validated_data['move_to'])
|
||||
schema.refresh_from_db()
|
||||
return Response(status=200, data=models.PyConceptAdapter(schema).full())
|
||||
schema.item.refresh_from_db()
|
||||
return Response(status=200, data=m.PyConceptAdapter(schema).full())
|
||||
|
||||
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
||||
def reset_aliases(self, request, pk):
|
||||
''' Endpoint: Recreate all aliases based on order. '''
|
||||
schema = self._get_schema()
|
||||
schema.reset_aliases()
|
||||
return Response(status=200, data=models.PyConceptAdapter(schema).full())
|
||||
return Response(status=200, data=m.PyConceptAdapter(schema).full())
|
||||
|
||||
@action(detail=True, methods=['patch'], url_path='load-trs')
|
||||
def load_trs(self, request, pk):
|
||||
''' Endpoint: Load data from file and replace current schema. '''
|
||||
serializer = serializers.RSFormUploadSerializer(data=request.data)
|
||||
serializer = s.RSFormUploadSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema = self._get_schema()
|
||||
load_metadata = serializer.validated_data['load_metadata']
|
||||
data = utils.read_trs(request.FILES['file'].file)
|
||||
data['id'] = schema.pk
|
||||
data['id'] = schema.item.pk
|
||||
|
||||
serializer = serializers.RSFormTRSSerializer(data=data, context={'load_meta': load_metadata})
|
||||
serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': load_metadata})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema = serializer.save()
|
||||
return Response(status=200, data=models.PyConceptAdapter(schema).full())
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='clone')
|
||||
def clone(self, request, pk):
|
||||
''' Endpoint: Clone RSForm constituents and create new schema using new metadata. '''
|
||||
serializer = serializers.RSFormMetaSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
clone_data = serializers.RSFormTRSSerializer(self._get_schema()).data
|
||||
clone_data['owner'] = self.request.user
|
||||
clone_data['title'] = serializer.validated_data['title']
|
||||
clone_data['alias'] = serializer.validated_data.get('alias', '')
|
||||
clone_data['comment'] = serializer.validated_data.get('comment', '')
|
||||
clone_data['is_common'] = serializer.validated_data.get('is_common', False)
|
||||
|
||||
clone = serializers.RSFormTRSSerializer(data=clone_data, context={'load_meta': True})
|
||||
clone.is_valid(raise_exception=True)
|
||||
new_schema = clone.save()
|
||||
return Response(status=201, data=models.PyConceptAdapter(new_schema).full())
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def claim(self, request, pk=None):
|
||||
''' Endpoint: Claim ownership of RSForm. '''
|
||||
schema = self._get_schema()
|
||||
if schema.owner == self.request.user:
|
||||
return Response(status=304)
|
||||
else:
|
||||
schema.owner = self.request.user
|
||||
schema.save()
|
||||
return Response(status=200, data=serializers.RSFormMetaSerializer(schema).data)
|
||||
return Response(status=200, data=m.PyConceptAdapter(schema).full())
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def contents(self, request, pk):
|
||||
''' Endpoint: View schema db contents (including constituents). '''
|
||||
schema = serializers.RSFormContentsSerializer(self._get_schema()).data
|
||||
schema = s.RSFormSerializer(self._get_schema()).data
|
||||
return Response(schema)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def details(self, request, pk):
|
||||
''' Endpoint: Detailed schema view including statuses and parse. '''
|
||||
schema = self._get_schema()
|
||||
serializer = models.PyConceptAdapter(schema)
|
||||
serializer = m.PyConceptAdapter(schema)
|
||||
return Response(serializer.full())
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def check(self, request, pk):
|
||||
''' Endpoint: Check RSLang expression against schema context. '''
|
||||
schema = models.PyConceptAdapter(self._get_schema())
|
||||
serializer = serializers.ExpressionSerializer(data=request.data)
|
||||
schema = m.PyConceptAdapter(self._get_schema())
|
||||
serializer = s.ExpressionSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
expression = serializer.validated_data['expression']
|
||||
result = pyconcept.check_expression(json.dumps(schema.data), expression)
|
||||
|
@ -205,19 +226,19 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
def resolve(self, request, pk):
|
||||
''' Endpoint: Resolve refenrces in text against schema terms context. '''
|
||||
schema = self._get_schema()
|
||||
serializer = serializers.TextSerializer(data=request.data)
|
||||
serializer = s.TextSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
text = serializer.validated_data['text']
|
||||
resolver = schema.resolver()
|
||||
resolver.resolve(text)
|
||||
return Response(status=200, data=serializers.ResolverSerializer(resolver).data)
|
||||
return Response(status=200, data=s.ResolverSerializer(resolver).data)
|
||||
|
||||
@action(detail=True, methods=['get'], url_path='export-trs')
|
||||
def export_trs(self, request, pk):
|
||||
''' Endpoint: Download Exteor compatible file. '''
|
||||
schema = serializers.RSFormTRSSerializer(self._get_schema()).data
|
||||
schema = s.RSFormTRSSerializer(self._get_schema()).data
|
||||
trs = utils.write_trs(schema)
|
||||
filename = self._get_schema().alias
|
||||
filename = self._get_schema().item.alias
|
||||
if filename == '' or not filename.isascii():
|
||||
# Note: non-ascii symbols in Content-Disposition
|
||||
# are not supported by some browsers
|
||||
|
@ -231,7 +252,7 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
|
||||
class TrsImportView(views.APIView):
|
||||
''' Endpoint: Upload RS form in Exteor format. '''
|
||||
serializer_class = serializers.FileSerializer
|
||||
serializer_class = s.FileSerializer
|
||||
|
||||
def post(self, request):
|
||||
data = utils.read_trs(request.FILES['file'].file)
|
||||
|
@ -239,10 +260,10 @@ class TrsImportView(views.APIView):
|
|||
if owner.is_anonymous:
|
||||
owner = None
|
||||
_prepare_rsform_data(data, request, owner)
|
||||
serializer = serializers.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
||||
serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema = serializer.save()
|
||||
result = serializers.RSFormMetaSerializer(schema)
|
||||
result = s.LibraryItemSerializer(schema.item)
|
||||
return Response(status=201, data=result.data)
|
||||
|
||||
|
||||
|
@ -253,25 +274,27 @@ def create_rsform(request):
|
|||
if owner.is_anonymous:
|
||||
owner = None
|
||||
if 'file' not in request.FILES:
|
||||
serializer = serializers.RSFormMetaSerializer(data=request.data)
|
||||
serializer = s.LibraryItemSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema = models.RSForm.objects.create(
|
||||
schema = m.RSForm.create(
|
||||
title=serializer.validated_data['title'],
|
||||
owner=owner,
|
||||
alias=serializer.validated_data.get('alias', ''),
|
||||
comment=serializer.validated_data.get('comment', ''),
|
||||
is_common=serializer.validated_data.get('is_common', False),
|
||||
is_canonical=serializer.validated_data.get('is_canonical', False),
|
||||
)
|
||||
else:
|
||||
data = utils.read_trs(request.FILES['file'].file)
|
||||
_prepare_rsform_data(data, request, owner)
|
||||
serializer = serializers.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
||||
serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema = serializer.save()
|
||||
result = serializers.RSFormMetaSerializer(schema)
|
||||
result = s.LibraryItemSerializer(schema.item)
|
||||
return Response(status=201, data=result.data)
|
||||
|
||||
def _prepare_rsform_data(data: dict, request, owner: models.User):
|
||||
def _prepare_rsform_data(data: dict, request, owner: m.User):
|
||||
data['owner'] = owner
|
||||
if 'title' in request.data and request.data['title'] != '':
|
||||
data['title'] = request.data['title']
|
||||
if data['title'] == '':
|
||||
|
@ -280,16 +303,21 @@ def _prepare_rsform_data(data: dict, request, owner: models.User):
|
|||
data['alias'] = request.data['alias']
|
||||
if 'comment' in request.data and request.data['comment'] != '':
|
||||
data['comment'] = request.data['comment']
|
||||
|
||||
is_common = True
|
||||
if 'is_common' in request.data:
|
||||
is_common = request.data['is_common'] == 'true'
|
||||
data['is_common'] = is_common
|
||||
data['owner'] = owner
|
||||
|
||||
is_canonical = False
|
||||
if 'is_canonical' in request.data:
|
||||
is_canonical = request.data['is_canonical'] == 'true'
|
||||
data['is_canonical'] = is_canonical
|
||||
|
||||
@api_view(['POST'])
|
||||
def parse_expression(request):
|
||||
''' Endpoint: Parse RS expression. '''
|
||||
serializer = serializers.ExpressionSerializer(data=request.data)
|
||||
serializer = s.ExpressionSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
expression = serializer.validated_data['expression']
|
||||
result = pyconcept.parse_expression(expression)
|
||||
|
@ -299,7 +327,7 @@ def parse_expression(request):
|
|||
@api_view(['POST'])
|
||||
def convert_to_ascii(request):
|
||||
''' Endpoint: Convert expression to ASCII syntax. '''
|
||||
serializer = serializers.ExpressionSerializer(data=request.data)
|
||||
serializer = s.ExpressionSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
expression = serializer.validated_data['expression']
|
||||
result = pyconcept.convert_to_ascii(expression)
|
||||
|
@ -309,7 +337,7 @@ def convert_to_ascii(request):
|
|||
@api_view(['POST'])
|
||||
def convert_to_math(request):
|
||||
''' Endpoint: Convert expression to MATH syntax. '''
|
||||
serializer = serializers.ExpressionSerializer(data=request.data)
|
||||
serializer = s.ExpressionSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
expression = serializer.validated_data['expression']
|
||||
result = pyconcept.convert_to_math(expression)
|
||||
|
|
10513
rsconcept/backend/fixtures/InitialData.json
Normal file
10513
rsconcept/backend/fixtures/InitialData.json
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -13,18 +13,12 @@ def load_initial_schemas(apps, schema_editor):
|
|||
for file in files:
|
||||
data = utils.read_trs(os.path.join(subdir, file))
|
||||
data['is_common'] = True
|
||||
data['is_canonical'] = True
|
||||
serializer = RSFormTRSSerializer(data=data, context={'load_meta': True})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
||||
|
||||
def load_initial_users(apps, schema_editor):
|
||||
for n in range(1, 10, 1):
|
||||
User.objects.create_user(
|
||||
f'TestUser{n}', f'usermail{n}@gmail.com', '1234'
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
@ -35,5 +29,4 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.RunPython(load_initial_schemas),
|
||||
migrations.RunPython(load_initial_users),
|
||||
]
|
|
@ -1,19 +1,19 @@
|
|||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { ErrorInfo } from '../components/BackendError';
|
||||
import { DataCallback, deleteRSForm, getLibrary, postCloneRSForm, postNewRSForm } from '../utils/backendAPI';
|
||||
import { ILibraryFilter, IRSFormCreateData, IRSFormData, IRSFormMeta, matchRSFormMeta } from '../utils/models';
|
||||
import { DataCallback, deleteLibraryItem, getLibrary, postCloneLibraryItem, postNewRSForm } from '../utils/backendAPI';
|
||||
import { ILibraryFilter, ILibraryItem, IRSFormCreateData, IRSFormData, matchLibraryItem } from '../utils/models';
|
||||
import { useAuth } from './AuthContext';
|
||||
|
||||
interface ILibraryContext {
|
||||
items: IRSFormMeta[]
|
||||
items: ILibraryItem[]
|
||||
loading: boolean
|
||||
processing: boolean
|
||||
error: ErrorInfo
|
||||
setError: (error: ErrorInfo) => void
|
||||
|
||||
filter: (params: ILibraryFilter) => IRSFormMeta[]
|
||||
createSchema: (data: IRSFormCreateData, callback?: DataCallback<IRSFormMeta>) => void
|
||||
filter: (params: ILibraryFilter) => ILibraryItem[]
|
||||
createSchema: (data: IRSFormCreateData, callback?: DataCallback<ILibraryItem>) => void
|
||||
cloneSchema: (target: number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => void
|
||||
destroySchema: (target: number, callback?: () => void) => void
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ interface LibraryStateProps {
|
|||
}
|
||||
|
||||
export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||
const [ items, setItems ] = useState<IRSFormMeta[]>([])
|
||||
const [ items, setItems ] = useState<ILibraryItem[]>([])
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
const [ processing, setProcessing ] = useState(false);
|
||||
const [ error, setError ] = useState<ErrorInfo>(undefined);
|
||||
|
@ -44,13 +44,13 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
(params: ILibraryFilter) => {
|
||||
let result = items;
|
||||
if (params.ownedBy) {
|
||||
result = result.filter(schema => schema.owner === params.ownedBy);
|
||||
result = result.filter(item => item.owner === params.ownedBy);
|
||||
}
|
||||
if (params.is_common !== undefined) {
|
||||
result = result.filter(schema => schema.is_common === params.is_common);
|
||||
result = result.filter(item => item.is_common === params.is_common);
|
||||
}
|
||||
if (params.queryMeta) {
|
||||
result = result.filter(schema => matchRSFormMeta(params.queryMeta!, schema));
|
||||
result = result.filter(item => matchLibraryItem(params.queryMeta!, item));
|
||||
}
|
||||
return result;
|
||||
}, [items]);
|
||||
|
@ -75,13 +75,13 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
}, [reload, user]);
|
||||
|
||||
const createSchema = useCallback(
|
||||
(data: IRSFormCreateData, callback?: DataCallback<IRSFormMeta>) => {
|
||||
(data: IRSFormCreateData, callback?: DataCallback<ILibraryItem>) => {
|
||||
setError(undefined);
|
||||
postNewRSForm({
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error); },
|
||||
onError: error => setError(error),
|
||||
onSuccess: newSchema => {
|
||||
reload();
|
||||
if (callback) callback(newSchema);
|
||||
|
@ -92,7 +92,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
const destroySchema = useCallback(
|
||||
(target: number, callback?: () => void) => {
|
||||
setError(undefined)
|
||||
deleteRSForm(String(target), {
|
||||
deleteLibraryItem(String(target), {
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => setError(error),
|
||||
|
@ -108,7 +108,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
return;
|
||||
}
|
||||
setError(undefined)
|
||||
postCloneRSForm(String(target), {
|
||||
postCloneLibraryItem(String(target), {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
|
|
|
@ -6,14 +6,14 @@ import { useRSFormDetails } from '../hooks/useRSFormDetails'
|
|||
import {
|
||||
type DataCallback, getTRSFile,
|
||||
patchConstituenta, patchDeleteConstituenta,
|
||||
patchLibraryItem,
|
||||
patchMoveConstituenta, patchRenameConstituenta,
|
||||
patchResetAliases, patchRSForm,
|
||||
patchUploadTRS, postClaimRSForm, postNewConstituenta
|
||||
patchResetAliases, patchUploadTRS, postClaimLibraryItem, postNewConstituenta
|
||||
} from '../utils/backendAPI'
|
||||
import {
|
||||
IConstituentaList, IConstituentaMeta, ICstCreateData,
|
||||
ICstMovetoData, ICstRenameData, ICstUpdateData, IRSForm,
|
||||
IRSFormMeta, IRSFormUpdateData, IRSFormUploadData
|
||||
ICstMovetoData, ICstRenameData, ICstUpdateData, ILibraryItem,
|
||||
ILibraryUpdateData, IRSForm, IRSFormUploadData
|
||||
} from '../utils/models'
|
||||
import { useAuth } from './AuthContext'
|
||||
|
||||
|
@ -35,8 +35,8 @@ interface IRSFormContext {
|
|||
toggleReadonly: () => void
|
||||
toggleTracking: () => void
|
||||
|
||||
update: (data: IRSFormUpdateData, callback?: DataCallback<IRSFormMeta>) => void
|
||||
claim: (callback?: DataCallback<IRSFormMeta>) => void
|
||||
update: (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => void
|
||||
claim: (callback?: DataCallback<ILibraryItem>) => void
|
||||
download: (callback: DataCallback<Blob>) => void
|
||||
upload: (data: IRSFormUploadData, callback: () => void) => void
|
||||
|
||||
|
@ -73,33 +73,41 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
const [ isForceAdmin, setIsForceAdmin ] = useState(false);
|
||||
const [ isReadonly, setIsReadonly ] = useState(false);
|
||||
|
||||
const isOwned = useMemo(() => user?.id === schema?.owner || false, [user, schema?.owner]);
|
||||
const isClaimable = useMemo(() => user?.id !== schema?.owner || false, [user, schema?.owner]);
|
||||
const isOwned = useMemo(
|
||||
() => {
|
||||
return user?.id === schema?.owner || false;
|
||||
}, [user, schema?.owner]);
|
||||
|
||||
const isClaimable = useMemo(
|
||||
() => {
|
||||
return (user?.id !== schema?.owner && schema?.is_common && !schema?.is_canonical) ?? false;
|
||||
}, [user, schema?.owner, schema?.is_common, schema?.is_canonical]);
|
||||
|
||||
const isEditable = useMemo(
|
||||
() => {
|
||||
return (
|
||||
!loading && !processing && !isReadonly &&
|
||||
() => {
|
||||
return (
|
||||
!loading && !processing && !isReadonly &&
|
||||
((isOwned || (isForceAdmin && user?.is_staff)) ?? false)
|
||||
)
|
||||
}, [user?.is_staff, isReadonly, isForceAdmin, isOwned, loading, processing])
|
||||
);
|
||||
}, [user?.is_staff, isReadonly, isForceAdmin, isOwned, loading, processing]);
|
||||
|
||||
const isTracking = useMemo(
|
||||
() => {
|
||||
return true
|
||||
}, []);
|
||||
() => {
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const toggleTracking = useCallback(
|
||||
() => {
|
||||
toast.info('Отслеживание в разработке...')
|
||||
}, []);
|
||||
() => {
|
||||
toast.info('Отслеживание в разработке...')
|
||||
}, []);
|
||||
|
||||
const update = useCallback(
|
||||
(data: IRSFormUpdateData, callback?: DataCallback<IRSFormMeta>) => {
|
||||
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
setError(undefined)
|
||||
patchRSForm(schemaID, {
|
||||
patchLibraryItem(schemaID, {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
|
@ -130,12 +138,12 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
}, [schemaID, setError, setSchema, schema]);
|
||||
|
||||
const claim = useCallback(
|
||||
(callback?: DataCallback<IRSFormMeta>) => {
|
||||
(callback?: DataCallback<ILibraryItem>) => {
|
||||
if (!schema || !user) {
|
||||
return;
|
||||
}
|
||||
setError(undefined)
|
||||
postClaimRSForm(schemaID, {
|
||||
postClaimLibraryItem(schemaID, {
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => setError(error),
|
||||
|
|
|
@ -11,7 +11,7 @@ import TextArea from '../components/Common/TextArea';
|
|||
import TextInput from '../components/Common/TextInput';
|
||||
import RequireAuth from '../components/RequireAuth';
|
||||
import { useLibrary } from '../context/LibraryContext';
|
||||
import { IRSFormCreateData } from '../utils/models';
|
||||
import { IRSFormCreateData, LibraryItemType } from '../utils/models';
|
||||
|
||||
function CreateRSFormPage() {
|
||||
const navigate = useNavigate();
|
||||
|
@ -41,10 +41,12 @@ function CreateRSFormPage() {
|
|||
return;
|
||||
}
|
||||
const data: IRSFormCreateData = {
|
||||
item_type: LibraryItemType.RSFORM,
|
||||
title: title,
|
||||
alias: alias,
|
||||
comment: comment,
|
||||
is_common: common,
|
||||
is_canonical: false,
|
||||
file: file,
|
||||
fileName: file?.name
|
||||
};
|
||||
|
|
|
@ -6,19 +6,19 @@ import ConceptDataTable from '../../components/Common/ConceptDataTable';
|
|||
import TextURL from '../../components/Common/TextURL';
|
||||
import { useNavSearch } from '../../context/NavSearchContext';
|
||||
import { useUsers } from '../../context/UsersContext';
|
||||
import { IRSFormMeta } from '../../utils/models'
|
||||
import { ILibraryItem } from '../../utils/models'
|
||||
|
||||
interface ViewLibraryProps {
|
||||
schemas: IRSFormMeta[]
|
||||
items: ILibraryItem[]
|
||||
}
|
||||
|
||||
function ViewLibrary({ schemas }: ViewLibraryProps) {
|
||||
function ViewLibrary({ items }: ViewLibraryProps) {
|
||||
const { resetQuery: cleanQuery } = useNavSearch();
|
||||
const navigate = useNavigate();
|
||||
const intl = useIntl();
|
||||
const { getUserLabel } = useUsers();
|
||||
|
||||
const openRSForm = (schema: IRSFormMeta) => navigate(`/rsforms/${schema.id}`);
|
||||
const openRSForm = (item: ILibraryItem) => navigate(`/rsforms/${item.id}`);
|
||||
|
||||
const columns = useMemo(() =>
|
||||
[
|
||||
|
@ -26,7 +26,17 @@ function ViewLibrary({ schemas }: ViewLibraryProps) {
|
|||
name: 'Шифр',
|
||||
id: 'alias',
|
||||
maxWidth: '140px',
|
||||
selector: (schema: IRSFormMeta) => schema.alias,
|
||||
selector: (item: ILibraryItem) => item.alias,
|
||||
sortable: true,
|
||||
reorder: true
|
||||
},
|
||||
{
|
||||
name: 'Статусы',
|
||||
id: 'status',
|
||||
maxWidth: '50px',
|
||||
selector: (item: ILibraryItem) => {
|
||||
return `${item.is_canonical ? 'C': ''}${item.is_common ? 'S': ''}`
|
||||
},
|
||||
sortable: true,
|
||||
reorder: true
|
||||
},
|
||||
|
@ -34,16 +44,16 @@ function ViewLibrary({ schemas }: ViewLibraryProps) {
|
|||
name: 'Название',
|
||||
id: 'title',
|
||||
minWidth: '50%',
|
||||
selector: (schema: IRSFormMeta) => schema.title,
|
||||
selector: (item: ILibraryItem) => item.title,
|
||||
sortable: true,
|
||||
reorder: true
|
||||
},
|
||||
{
|
||||
name: 'Владелец',
|
||||
id: 'owner',
|
||||
selector: (schema: IRSFormMeta) => schema.owner ?? 0,
|
||||
format: (schema: IRSFormMeta) => {
|
||||
return getUserLabel(schema.owner);
|
||||
selector: (item: ILibraryItem) => item.owner ?? 0,
|
||||
format: (item: ILibraryItem) => {
|
||||
return getUserLabel(item.owner);
|
||||
},
|
||||
sortable: true,
|
||||
reorder: true
|
||||
|
@ -51,8 +61,8 @@ function ViewLibrary({ schemas }: ViewLibraryProps) {
|
|||
{
|
||||
name: 'Обновлена',
|
||||
id: 'time_update',
|
||||
selector: (schema: IRSFormMeta) => schema.time_update,
|
||||
format: (schema: IRSFormMeta) => new Date(schema.time_update).toLocaleString(intl.locale),
|
||||
selector: (item: ILibraryItem) => item.time_update,
|
||||
format: (item: ILibraryItem) => new Date(item.time_update).toLocaleString(intl.locale),
|
||||
sortable: true,
|
||||
reorder: true
|
||||
}
|
||||
|
@ -61,7 +71,7 @@ function ViewLibrary({ schemas }: ViewLibraryProps) {
|
|||
return (
|
||||
<ConceptDataTable
|
||||
columns={columns}
|
||||
data={schemas}
|
||||
data={items}
|
||||
defaultSortFieldId='time_update'
|
||||
defaultSortAsc={false}
|
||||
striped
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Loader } from '../../components/Common/Loader'
|
|||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useNavSearch } from '../../context/NavSearchContext';
|
||||
import { ILibraryFilter, IRSFormMeta } from '../../utils/models';
|
||||
import { ILibraryFilter, ILibraryItem } from '../../utils/models';
|
||||
import ViewLibrary from './ViewLibrary';
|
||||
|
||||
function LibraryPage() {
|
||||
|
@ -16,7 +16,7 @@ function LibraryPage() {
|
|||
const library = useLibrary();
|
||||
|
||||
const [ filterParams, setFilterParams ] = useState<ILibraryFilter>({});
|
||||
const [ items, setItems ] = useState<IRSFormMeta[]>([]);
|
||||
const [ items, setItems ] = useState<ILibraryItem[]>([]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const filterType = new URLSearchParams(search).get('filter');
|
||||
|
@ -43,7 +43,7 @@ function LibraryPage() {
|
|||
<div className='w-full'>
|
||||
{ library.loading && <Loader /> }
|
||||
{ library.error && <BackendError error={library.error} />}
|
||||
{ !library.loading && library.items && <ViewLibrary schemas={items} /> }
|
||||
{ !library.loading && library.items && <ViewLibrary items={items} /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,16 +21,18 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
|
|||
const [alias, setAlias] = useState('');
|
||||
const [comment, setComment] = useState('');
|
||||
const [common, setCommon] = useState(false);
|
||||
const [canonical, setCanonical] = useState(false);
|
||||
|
||||
const { cloneSchema } = useLibrary();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (schema) {
|
||||
setTitle(getCloneTitle(schema))
|
||||
setAlias(schema.alias)
|
||||
setComment(schema.comment)
|
||||
setCommon(schema.is_common)
|
||||
setTitle(getCloneTitle(schema));
|
||||
setAlias(schema.alias);
|
||||
setComment(schema.comment);
|
||||
setCommon(schema.is_common);
|
||||
setCanonical(false);
|
||||
}
|
||||
}, [schema, schema?.title, schema?.alias, schema?.comment, schema?.is_common]);
|
||||
|
||||
|
@ -39,10 +41,12 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
|
|||
return;
|
||||
}
|
||||
const data: IRSFormCreateData = {
|
||||
item_type: schema.item_type,
|
||||
title: title,
|
||||
alias: alias,
|
||||
comment: comment,
|
||||
is_common: common
|
||||
is_common: common,
|
||||
is_canonical: canonical
|
||||
};
|
||||
cloneSchema(schema.id, data, newSchema => {
|
||||
toast.success(`Схема создана: ${newSchema.alias}`);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -13,19 +13,21 @@ import { CrownIcon, DownloadIcon, DumpBinIcon, HelpIcon, SaveIcon, ShareIcon } f
|
|||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useUsers } from '../../context/UsersContext';
|
||||
import { IRSFormCreateData } from '../../utils/models';
|
||||
import { claimOwnershipProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
||||
import { IRSFormCreateData, LibraryItemType } from '../../utils/models';
|
||||
|
||||
interface EditorRSFormProps {
|
||||
onDestroy: () => void
|
||||
onClaim: () => void
|
||||
onShare: () => void
|
||||
onDownload: () => void
|
||||
}
|
||||
|
||||
function EditorRSForm({ onDestroy }: EditorRSFormProps) {
|
||||
function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormProps) {
|
||||
const intl = useIntl();
|
||||
const { getUserLabel } = useUsers();
|
||||
const {
|
||||
schema, update, download,
|
||||
isEditable, isOwned, isClaimable, processing, claim
|
||||
schema, update, isForceAdmin,
|
||||
isEditable, isOwned, isClaimable, processing
|
||||
} = useRSForm();
|
||||
const { user } = useAuth();
|
||||
|
||||
|
@ -33,6 +35,7 @@ function EditorRSForm({ onDestroy }: EditorRSFormProps) {
|
|||
const [alias, setAlias] = useState('');
|
||||
const [comment, setComment] = useState('');
|
||||
const [common, setCommon] = useState(false);
|
||||
const [canonical, setCanonical] = useState(false);
|
||||
|
||||
const [isModified, setIsModified] = useState(true);
|
||||
|
||||
|
@ -45,10 +48,12 @@ function EditorRSForm({ onDestroy }: EditorRSFormProps) {
|
|||
schema.title !== title ||
|
||||
schema.alias !== alias ||
|
||||
schema.comment !== comment ||
|
||||
schema.is_common !== common
|
||||
schema.is_common !== common ||
|
||||
schema.is_canonical !== canonical
|
||||
);
|
||||
}, [schema, schema?.title, schema?.alias, schema?.comment, schema?.is_common,
|
||||
title, alias, comment, common]);
|
||||
}, [schema, schema?.title, schema?.alias, schema?.comment,
|
||||
schema?.is_common, schema?.is_canonical,
|
||||
title, alias, comment, common, canonical]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (schema) {
|
||||
|
@ -56,25 +61,23 @@ function EditorRSForm({ onDestroy }: EditorRSFormProps) {
|
|||
setAlias(schema.alias);
|
||||
setComment(schema.comment);
|
||||
setCommon(schema.is_common);
|
||||
setCanonical(schema.is_canonical);
|
||||
}
|
||||
}, [schema]);
|
||||
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const data: IRSFormCreateData = {
|
||||
item_type: LibraryItemType.RSFORM,
|
||||
title: title,
|
||||
alias: alias,
|
||||
comment: comment,
|
||||
is_common: common
|
||||
is_common: common,
|
||||
is_canonical: canonical
|
||||
};
|
||||
update(data, () => toast.success('Изменения сохранены'));
|
||||
};
|
||||
|
||||
const handleDownload = useCallback(() => {
|
||||
const fileName = (schema?.alias ?? 'Schema') + '.trs';
|
||||
downloadRSFormProc(download, fileName);
|
||||
}, [download, schema?.alias]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border min-w-fit'>
|
||||
<div className='relative w-full'>
|
||||
|
@ -82,18 +85,18 @@ function EditorRSForm({ onDestroy }: EditorRSFormProps) {
|
|||
<MiniButton
|
||||
tooltip='Поделиться схемой'
|
||||
icon={<ShareIcon size={5} color='text-primary'/>}
|
||||
onClick={shareCurrentURLProc}
|
||||
onClick={onShare}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Скачать TRS файл'
|
||||
icon={<DownloadIcon size={5} color='text-primary'/>}
|
||||
onClick={handleDownload}
|
||||
onClick={onDownload}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
||||
icon={<CrownIcon size={5} color={isOwned ? '' : 'text-green'}/>}
|
||||
disabled={!isClaimable || !user}
|
||||
onClick={() => { claimOwnershipProc(claim); }}
|
||||
onClick={onClaim}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Удалить схему'
|
||||
|
@ -113,25 +116,35 @@ function EditorRSForm({ onDestroy }: EditorRSFormProps) {
|
|||
required
|
||||
value={title}
|
||||
disabled={!isEditable}
|
||||
onChange={event => { setTitle(event.target.value); }}
|
||||
onChange={event => setTitle(event.target.value)}
|
||||
/>
|
||||
<TextInput id='alias' label='Сокращение' type='text'
|
||||
required
|
||||
value={alias}
|
||||
disabled={!isEditable}
|
||||
widthClass='max-w-sm'
|
||||
onChange={event => { setAlias(event.target.value); }}
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<TextArea id='comment' label='Комментарий'
|
||||
value={comment}
|
||||
disabled={!isEditable}
|
||||
onChange={event => { setComment(event.target.value); }}
|
||||
/>
|
||||
<Checkbox id='common' label='Общедоступная схема'
|
||||
value={common}
|
||||
disabled={!isEditable}
|
||||
onChange={event => { setCommon(event.target.checked); }}
|
||||
onChange={event => setComment(event.target.value)}
|
||||
/>
|
||||
<div className='flex justify-between whitespace-nowrap'>
|
||||
<div></div>
|
||||
<Checkbox id='common' label='Общедоступная схема'
|
||||
value={common}
|
||||
disabled={!isEditable}
|
||||
onChange={event => setCommon(event.target.checked)}
|
||||
/>
|
||||
<Checkbox id='canonical' label='Библиотечная схема'
|
||||
widthClass='w-fit'
|
||||
value={canonical}
|
||||
tooltip='Только администраторы могут присваивать схемам библиотечный статус'
|
||||
disabled={!isEditable || !isForceAdmin}
|
||||
onChange={event => setCanonical(event.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center justify-between gap-1 py-2 mt-2'>
|
||||
<SubmitButton
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import fileDownload from 'js-file-download';
|
||||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
@ -35,7 +36,7 @@ function RSTabs() {
|
|||
const navigate = useNavigate();
|
||||
const search = useLocation().search;
|
||||
const {
|
||||
error, schema, loading,
|
||||
error, schema, loading, claim, download,
|
||||
cstCreate, cstDelete, cstRename
|
||||
} = useRSForm();
|
||||
const { destroySchema } = useLibrary();
|
||||
|
@ -195,6 +196,35 @@ function RSTabs() {
|
|||
});
|
||||
}, [schema, destroySchema, navigate]);
|
||||
|
||||
const onClaimSchema = useCallback(
|
||||
() => {
|
||||
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
||||
return;
|
||||
}
|
||||
claim(() => toast.success('Вы стали владельцем схемы'));
|
||||
}, [claim]);
|
||||
|
||||
const onShareSchema = useCallback(
|
||||
() => {
|
||||
const url = window.location.href + '&share';
|
||||
navigator.clipboard.writeText(url)
|
||||
.then(() => toast.success(`Ссылка скопирована: ${url}`))
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
|
||||
const onDownloadSchema = useCallback(
|
||||
() => {
|
||||
const fileName = (schema?.alias ?? 'Schema') + '.trs';
|
||||
download(
|
||||
(data) => {
|
||||
try {
|
||||
fileDownload(data, fileName);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}, [schema?.alias, download]);
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
{ loading && <Loader /> }
|
||||
|
@ -240,7 +270,10 @@ function RSTabs() {
|
|||
>
|
||||
<TabList className='flex items-start pl-2 select-none w-fit clr-bg-pop'>
|
||||
<RSTabsMenu
|
||||
onDownload={onDownloadSchema}
|
||||
onDestroy={onDestroySchema}
|
||||
onClaim={onClaimSchema}
|
||||
onShare={onShareSchema}
|
||||
showCloneDialog={() => setShowClone(true)}
|
||||
showUploadDialog={() => setShowUpload(true)}
|
||||
/>
|
||||
|
@ -255,7 +288,10 @@ function RSTabs() {
|
|||
|
||||
<TabPanel className='flex items-start w-full gap-2 px-2'>
|
||||
<EditorRSForm
|
||||
onDownload={onDownloadSchema}
|
||||
onDestroy={onDestroySchema}
|
||||
onClaim={onClaimSchema}
|
||||
onShare={onShareSchema}
|
||||
/>
|
||||
{schema.stats && <RSFormStats stats={schema.stats}/>}
|
||||
</TabPanel>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import Button from '../../components/Common/Button';
|
||||
|
@ -9,55 +8,57 @@ import { CloneIcon, CrownIcon, DownloadIcon, DumpBinIcon, EyeIcon, EyeOffIcon, M
|
|||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useDropdown from '../../hooks/useDropdown';
|
||||
import { claimOwnershipProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
||||
|
||||
interface RSTabsMenuProps {
|
||||
showUploadDialog: () => void
|
||||
showCloneDialog: () => void
|
||||
onDestroy: () => void
|
||||
onClaim: () => void
|
||||
onShare: () => void
|
||||
onDownload: () => void
|
||||
}
|
||||
|
||||
function RSTabsMenu({showUploadDialog, showCloneDialog, onDestroy}: RSTabsMenuProps) {
|
||||
function RSTabsMenu({
|
||||
showUploadDialog, showCloneDialog,
|
||||
onDestroy, onShare, onDownload, onClaim
|
||||
}: RSTabsMenuProps) {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const {
|
||||
schema,
|
||||
isOwned, isEditable, isTracking, isReadonly: readonly, isForceAdmin: forceAdmin,
|
||||
toggleTracking, toggleForceAdmin, toggleReadonly,
|
||||
claim, download
|
||||
toggleTracking, toggleForceAdmin, toggleReadonly
|
||||
} = useRSForm();
|
||||
const schemaMenu = useDropdown();
|
||||
const editMenu = useDropdown();
|
||||
|
||||
const handleClaimOwner = useCallback(() => {
|
||||
function handleClaimOwner() {
|
||||
editMenu.hide();
|
||||
claimOwnershipProc(claim)
|
||||
}, [claim, editMenu]);
|
||||
onClaim();
|
||||
}
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
function handleDelete() {
|
||||
schemaMenu.hide();
|
||||
onDestroy();
|
||||
}, [onDestroy, schemaMenu]);
|
||||
}
|
||||
|
||||
const handleDownload = useCallback(() => {
|
||||
function handleDownload () {
|
||||
schemaMenu.hide();
|
||||
const fileName = (schema?.alias ?? 'Schema') + '.trs';
|
||||
downloadRSFormProc(download, fileName);
|
||||
}, [schemaMenu, download, schema?.alias]);
|
||||
onDownload();
|
||||
}
|
||||
|
||||
const handleUpload = useCallback(() => {
|
||||
function handleUpload() {
|
||||
schemaMenu.hide();
|
||||
showUploadDialog();
|
||||
}, [schemaMenu, showUploadDialog]);
|
||||
}
|
||||
|
||||
const handleClone = useCallback(() => {
|
||||
function handleClone() {
|
||||
schemaMenu.hide();
|
||||
showCloneDialog();
|
||||
}, [schemaMenu, showCloneDialog]);
|
||||
}
|
||||
|
||||
function handleShare() {
|
||||
schemaMenu.hide();
|
||||
shareCurrentURLProc();
|
||||
onShare();
|
||||
}
|
||||
|
||||
function handleCreateNew() {
|
||||
|
|
|
@ -6,9 +6,8 @@ import { config } from './constants'
|
|||
import {
|
||||
IConstituentaList, IConstituentaMeta,
|
||||
ICstCreateData, ICstCreatedResponse, ICstMovetoData, ICstRenameData, ICstUpdateData,
|
||||
ICurrentUser, IExpressionParse, IReferenceData, IRefsText, IRSExpression,
|
||||
IRSFormCreateData, IRSFormData,
|
||||
IRSFormMeta, IRSFormUpdateData, IRSFormUploadData, IUserInfo,
|
||||
ICurrentUser, IExpressionParse, ILibraryItem, ILibraryUpdateData, IReferenceData, IRefsText,
|
||||
IRSExpression, IRSFormCreateData, IRSFormData, IRSFormUploadData, IUserInfo,
|
||||
IUserLoginData, IUserProfile, IUserSignupData, IUserUpdateData, IUserUpdatePassword
|
||||
} from './models'
|
||||
|
||||
|
@ -116,15 +115,15 @@ export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
|||
});
|
||||
}
|
||||
|
||||
export function getLibrary(request: FrontPull<IRSFormMeta[]>) {
|
||||
export function getLibrary(request: FrontPull<ILibraryItem[]>) {
|
||||
AxiosGet({
|
||||
title: 'Available RSForms (Library) list',
|
||||
endpoint: '/api/library/',
|
||||
endpoint: '/api/library/active/',
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function postNewRSForm(request: FrontExchange<IRSFormCreateData, IRSFormMeta>) {
|
||||
export function postNewRSForm(request: FrontExchange<IRSFormCreateData, ILibraryItem>) {
|
||||
AxiosPost({
|
||||
title: 'New RSForm',
|
||||
endpoint: '/api/rsforms/create-detailed/',
|
||||
|
@ -137,10 +136,10 @@ export function postNewRSForm(request: FrontExchange<IRSFormCreateData, IRSFormM
|
|||
});
|
||||
}
|
||||
|
||||
export function postCloneRSForm(schema: string, request: FrontExchange<IRSFormCreateData, IRSFormData>) {
|
||||
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCreateData, IRSFormData>) {
|
||||
AxiosPost({
|
||||
title: 'clone RSForm',
|
||||
endpoint: `/api/rsforms/${schema}/clone/`,
|
||||
endpoint: `/api/library/${target}/clone/`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
@ -153,26 +152,26 @@ export function getRSFormDetails(target: string, request: FrontPull<IRSFormData>
|
|||
});
|
||||
}
|
||||
|
||||
export function patchRSForm(target: string, request: FrontExchange<IRSFormUpdateData, IRSFormMeta>) {
|
||||
export function patchLibraryItem(target: string, request: FrontExchange<ILibraryUpdateData, ILibraryItem>) {
|
||||
AxiosPatch({
|
||||
title: `RSForm id=${target}`,
|
||||
endpoint: `/api/rsforms/${target}/`,
|
||||
endpoint: `/api/library/${target}/`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteRSForm(target: string, request: FrontAction) {
|
||||
export function deleteLibraryItem(target: string, request: FrontAction) {
|
||||
AxiosDelete({
|
||||
title: `RSForm id=${target}`,
|
||||
endpoint: `/api/rsforms/${target}/`,
|
||||
endpoint: `/api/library/${target}/`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function postClaimRSForm(target: string, request: FrontPull<IRSFormMeta>) {
|
||||
export function postClaimLibraryItem(target: string, request: FrontPull<ILibraryItem>) {
|
||||
AxiosPost({
|
||||
title: `Claim on RSForm id=${target}`,
|
||||
endpoint: `/api/rsforms/${target}/claim/`,
|
||||
endpoint: `/api/library/${target}/claim/`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
|
|
@ -214,6 +214,29 @@ export interface ICstCreatedResponse {
|
|||
schema: IRSFormData
|
||||
}
|
||||
|
||||
// ========== LibraryItem ============
|
||||
export enum LibraryItemType {
|
||||
RSFORM = 'rsform',
|
||||
OPERATIONS_SCHEMA = 'oss'
|
||||
}
|
||||
|
||||
export interface ILibraryItem {
|
||||
id: number
|
||||
item_type: LibraryItemType
|
||||
title: string
|
||||
alias: string
|
||||
comment: string
|
||||
is_common: boolean
|
||||
is_canonical: boolean
|
||||
time_create: string
|
||||
time_update: string
|
||||
owner: number | null
|
||||
}
|
||||
|
||||
export interface ILibraryUpdateData
|
||||
extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'id' | 'owner'> {}
|
||||
|
||||
|
||||
// ========== RSForm ============
|
||||
export interface IRSFormStats {
|
||||
count_all: number
|
||||
|
@ -233,28 +256,17 @@ export interface IRSFormStats {
|
|||
count_theorem: number
|
||||
}
|
||||
|
||||
export interface IRSForm {
|
||||
id: number
|
||||
title: string
|
||||
alias: string
|
||||
comment: string
|
||||
is_common: boolean
|
||||
time_create: string
|
||||
time_update: string
|
||||
owner: number | null
|
||||
export interface IRSForm
|
||||
extends ILibraryItem {
|
||||
items: IConstituenta[]
|
||||
stats: IRSFormStats
|
||||
graph: Graph
|
||||
}
|
||||
|
||||
export interface IRSFormData extends Omit<IRSForm, 'stats' | 'graph'> {}
|
||||
export interface IRSFormMeta extends Omit<IRSForm, 'items' | 'stats' | 'graph'> {}
|
||||
|
||||
export interface IRSFormUpdateData
|
||||
extends Omit<IRSFormMeta, 'time_create' | 'time_update' | 'id' | 'owner'> {}
|
||||
|
||||
export interface IRSFormCreateData
|
||||
extends IRSFormUpdateData {
|
||||
extends ILibraryUpdateData {
|
||||
file?: File
|
||||
fileName?: string
|
||||
}
|
||||
|
@ -454,7 +466,7 @@ export function matchConstituenta(query: string, target: IConstituenta, mode: Cs
|
|||
return false;
|
||||
}
|
||||
|
||||
export function matchRSFormMeta(query: string, target: IRSFormMeta) {
|
||||
export function matchLibraryItem(query: string, target: ILibraryItem): boolean {
|
||||
const queryI = query.toUpperCase();
|
||||
if (target.alias.toUpperCase().match(queryI)) {
|
||||
return true;
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import fileDownload from 'js-file-download';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { type DataCallback } from './backendAPI';
|
||||
import { IRSFormMeta } from './models';
|
||||
|
||||
export function shareCurrentURLProc() {
|
||||
const url = window.location.href + '&share';
|
||||
navigator.clipboard.writeText(url)
|
||||
.then(() => toast.success(`Ссылка скопирована: ${url}`))
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
export function claimOwnershipProc(
|
||||
claim: (callback: DataCallback<IRSFormMeta>) => void
|
||||
) {
|
||||
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
||||
return;
|
||||
}
|
||||
claim(() => toast.success('Вы стали владельцем схемы'));
|
||||
}
|
||||
|
||||
export function downloadRSFormProc(
|
||||
download: (callback: DataCallback<Blob>) => void,
|
||||
fileName: string
|
||||
) {
|
||||
download((data) => {
|
||||
try {
|
||||
fileDownload(data, fileName);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user