Add OSS creation and fix access policy implementation

This commit is contained in:
IRBorisov 2024-06-03 17:38:30 +03:00
parent 93d56ef4fa
commit 9e02d809a0
23 changed files with 368 additions and 206 deletions

View File

@ -60,3 +60,7 @@ def invalidPosition():
def constituentaNoStructure(): def constituentaNoStructure():
return 'Указанная конституента не обладает теоретико-множественной типизацией' return 'Указанная конституента не обладает теоретико-множественной типизацией'
def missingFile():
return 'Отсутствует прикрепленный файл'

View File

@ -57,6 +57,8 @@ class ItemEditor(ItemOwner):
''' Item permission: Editor or higher. ''' ''' Item permission: Editor or higher. '''
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool: def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool:
if request.user.is_anonymous:
return False
item = _extract_item(obj) item = _extract_item(obj)
if m.Editor.objects.filter( if m.Editor.objects.filter(
item=item, item=item,
@ -69,6 +71,9 @@ class ItemEditor(ItemOwner):
class ItemAnyone(ItemEditor): class ItemAnyone(ItemEditor):
''' Item permission: Anyone if public. ''' ''' Item permission: Anyone if public. '''
def has_permission(self, request: Request, view: APIView) -> bool:
return True
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool: def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool:
item = _extract_item(obj) item = _extract_item(obj)
if item.access_policy == m.AccessPolicy.PUBLIC: if item.access_policy == m.AccessPolicy.PUBLIC:

View File

@ -20,7 +20,7 @@ from .data_access import (
CstSubstituteSerializer, CstSubstituteSerializer,
CstTargetSerializer, CstTargetSerializer,
InlineSynthesisSerializer, InlineSynthesisSerializer,
LibraryItemBase, LibraryItemBaseSerializer,
LibraryItemCloneSerializer, LibraryItemCloneSerializer,
LibraryItemSerializer, LibraryItemSerializer,
RSFormParseSerializer, RSFormParseSerializer,

View File

@ -13,13 +13,13 @@ from .basics import CstParseSerializer
from .io_pyconcept import PyConceptAdapter from .io_pyconcept import PyConceptAdapter
class LibraryItemBase(serializers.ModelSerializer): class LibraryItemBaseSerializer(serializers.ModelSerializer):
''' Serializer: LibraryItem entry full access. ''' ''' Serializer: LibraryItem entry full access. '''
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
model = LibraryItem model = LibraryItem
fields = '__all__' fields = '__all__'
read_only_fields = ('id', 'item_type') read_only_fields = ('id',)
class LibraryItemSerializer(serializers.ModelSerializer): class LibraryItemSerializer(serializers.ModelSerializer):
@ -31,6 +31,16 @@ class LibraryItemSerializer(serializers.ModelSerializer):
read_only_fields = ('id', 'item_type', 'owner', 'location', 'access_policy') read_only_fields = ('id', 'item_type', 'owner', 'location', 'access_policy')
class LibraryItemCloneSerializer(serializers.ModelSerializer):
''' Serializer: LibraryItem cloning. '''
items = PKField(many=True, required=False, queryset=Constituenta.objects.all())
class Meta:
''' serializer metadata. '''
model = LibraryItem
exclude = ['id', 'item_type', 'owner']
class VersionSerializer(serializers.ModelSerializer): class VersionSerializer(serializers.ModelSerializer):
''' Serializer: Version data. ''' ''' Serializer: Version data. '''
class Meta: class Meta:
@ -220,7 +230,7 @@ class RSFormSerializer(serializers.ModelSerializer):
validated_data=new_cst.validated_data validated_data=new_cst.validated_data
) )
loaded_item = LibraryItemBase(data=data) loaded_item = LibraryItemBaseSerializer(data=data)
loaded_item.is_valid(raise_exception=True) loaded_item.is_valid(raise_exception=True)
loaded_item.update( loaded_item.update(
instance=cast(LibraryItem, self.instance), instance=cast(LibraryItem, self.instance),
@ -337,11 +347,6 @@ class CstListSerializer(serializers.Serializer):
return attrs return attrs
class LibraryItemCloneSerializer(LibraryItemBase):
''' Serializer: LibraryItem cloning. '''
items = PKField(many=True, required=False, queryset=Constituenta.objects.all())
class CstMoveSerializer(CstListSerializer): class CstMoveSerializer(CstListSerializer):
''' Serializer: Change constituenta position. ''' ''' Serializer: Change constituenta position. '''
move_to = serializers.IntegerField() move_to = serializers.IntegerField()

View File

@ -45,10 +45,28 @@ class TestLibraryViewset(EndpointTester):
@decl_endpoint('/api/library', method='post') @decl_endpoint('/api/library', method='post')
def test_create(self): def test_create(self):
data = {'title': 'Title'} data = {
'title': 'Title',
'alias': 'alias',
}
self.executeBadData(data)
data = {
'item_type': LibraryItemType.OPERATIONS_SCHEMA,
'title': 'Title',
'alias': 'alias',
'access_policy': AccessPolicy.PROTECTED,
'visible': False,
'read_only': True
}
response = self.executeCreated(data) response = self.executeCreated(data)
self.assertEqual(response.data['title'], 'Title')
self.assertEqual(response.data['owner'], self.user.pk) self.assertEqual(response.data['owner'], self.user.pk)
self.assertEqual(response.data['item_type'], data['item_type'])
self.assertEqual(response.data['title'], data['title'])
self.assertEqual(response.data['alias'], data['alias'])
self.assertEqual(response.data['access_policy'], data['access_policy'])
self.assertEqual(response.data['visible'], data['visible'])
self.assertEqual(response.data['read_only'], data['read_only'])
self.logout() self.logout()
data = {'title': 'Title2'} data = {'title': 'Title2'}
@ -273,6 +291,16 @@ class TestLibraryViewset(EndpointTester):
self.assertFalse(response_contains(response, self.unowned)) self.assertFalse(response_contains(response, self.unowned))
self.assertFalse(response_contains(response, self.owned)) self.assertFalse(response_contains(response, self.owned))
@decl_endpoint('/api/library', method='get')
def test_library_get(self):
non_schema = LibraryItem.objects.create(
item_type=LibraryItemType.OPERATIONS_SCHEMA,
title='Test4'
)
response = self.executeOK()
self.assertTrue(response_contains(response, non_schema))
self.assertTrue(response_contains(response, self.unowned))
self.assertTrue(response_contains(response, self.owned))
@decl_endpoint('/api/library/all', method='get') @decl_endpoint('/api/library/all', method='get')
def test_retrieve_all(self): def test_retrieve_all(self):

View File

@ -25,27 +25,17 @@ class TestRSFormViewset(EndpointTester):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.schema = RSForm.create(title='Test', alias='T1', owner=self.user) self.owned = RSForm.create(title='Test', alias='T1', owner=self.user)
self.schema_id = self.schema.item.pk self.owned_id = self.owned.item.pk
self.unowned = RSForm.create(title='Test2', alias='T2') self.unowned = RSForm.create(title='Test2', alias='T2')
self.unowned_id = self.unowned.item.pk self.unowned_id = self.unowned.item.pk
self.private = RSForm.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
self.private_id = self.private.item.pk
@decl_endpoint('/api/rsforms/create-detailed', method='post') @decl_endpoint('/api/rsforms/create-detailed', method='post')
def test_create_rsform_file(self): def test_create_rsform_file(self):
work_dir = os.path.dirname(os.path.abspath(__file__)) work_dir = os.path.dirname(os.path.abspath(__file__))
with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file:
data = {'file': file, 'title': 'Test123', 'comment': '123', 'alias': 'ks1'}
response = self.client.post(self.endpoint, data=data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['owner'], self.user.pk)
self.assertEqual(response.data['title'], 'Test123')
self.assertEqual(response.data['alias'], 'ks1')
self.assertEqual(response.data['comment'], '123')
@decl_endpoint('/api/rsforms/create-detailed', method='post')
def test_create_rsform_json(self):
data = { data = {
'title': 'Test123', 'title': 'Test123',
'comment': '123', 'comment': '123',
@ -54,17 +44,20 @@ class TestRSFormViewset(EndpointTester):
'access_policy': AccessPolicy.PROTECTED, 'access_policy': AccessPolicy.PROTECTED,
'visible': False 'visible': False
} }
response = self.executeCreated(data) self.executeBadData(data)
with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file:
data['file'] = file
response = self.client.post(self.endpoint, data=data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['owner'], self.user.pk) self.assertEqual(response.data['owner'], self.user.pk)
self.assertEqual(response.data['title'], data['title']) self.assertEqual(response.data['title'], data['title'])
self.assertEqual(response.data['alias'], data['alias']) self.assertEqual(response.data['alias'], data['alias'])
self.assertEqual(response.data['location'], data['location']) self.assertEqual(response.data['comment'], data['comment'])
self.assertEqual(response.data['access_policy'], data['access_policy'])
self.assertEqual(response.data['visible'], data['visible'])
@decl_endpoint('/api/rsforms', method='get') @decl_endpoint('/api/rsforms', method='get')
def test_list(self): def test_list_rsforms(self):
non_schema = LibraryItem.objects.create( non_schema = LibraryItem.objects.create(
item_type=LibraryItemType.OPERATIONS_SCHEMA, item_type=LibraryItemType.OPERATIONS_SCHEMA,
title='Test3' title='Test3'
@ -72,38 +65,41 @@ class TestRSFormViewset(EndpointTester):
response = self.executeOK() response = self.executeOK()
self.assertFalse(response_contains(response, non_schema)) self.assertFalse(response_contains(response, non_schema))
self.assertTrue(response_contains(response, self.unowned.item)) self.assertTrue(response_contains(response, self.unowned.item))
self.assertTrue(response_contains(response, self.schema.item)) self.assertTrue(response_contains(response, self.owned.item))
response = self.client.get('/api/library')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response_contains(response, non_schema))
self.assertTrue(response_contains(response, self.unowned.item))
self.assertTrue(response_contains(response, self.schema.item))
@decl_endpoint('/api/rsforms/{item}/contents', method='get') @decl_endpoint('/api/rsforms/{item}/contents', method='get')
def test_contents(self): def test_contents(self):
schema = RSForm.create(title='Title1') response = self.executeOK(item=self.owned_id)
schema.insert_new('X1') self.assertEqual(response.data['owner'], self.owned.item.owner.pk)
self.executeOK(item=schema.item.pk) self.assertEqual(response.data['title'], self.owned.item.title)
self.assertEqual(response.data['alias'], self.owned.item.alias)
self.assertEqual(response.data['location'], self.owned.item.location)
self.assertEqual(response.data['access_policy'], self.owned.item.access_policy)
self.assertEqual(response.data['visible'], self.owned.item.visible)
@decl_endpoint('/api/rsforms/{item}/details', method='get') @decl_endpoint('/api/rsforms/{item}/details', method='get')
def test_details(self): def test_details(self):
schema = RSForm.create(title='Test', owner=self.user) x1 = self.owned.insert_new(
x1 = schema.insert_new(
alias='X1', alias='X1',
term_raw='человек', term_raw='человек',
term_resolved='человек' term_resolved='человек'
) )
x2 = schema.insert_new( x2 = self.owned.insert_new(
alias='X2', alias='X2',
term_raw='@{X1|plur}', term_raw='@{X1|plur}',
term_resolved='люди' term_resolved='люди'
) )
response = self.executeOK(item=schema.item.pk) response = self.executeOK(item=self.owned_id)
self.assertEqual(response.data['title'], 'Test') self.assertEqual(response.data['owner'], self.owned.item.owner.pk)
self.assertEqual(response.data['title'], self.owned.item.title)
self.assertEqual(response.data['alias'], self.owned.item.alias)
self.assertEqual(response.data['location'], self.owned.item.location)
self.assertEqual(response.data['access_policy'], self.owned.item.access_policy)
self.assertEqual(response.data['visible'], self.owned.item.visible)
self.assertEqual(len(response.data['items']), 2) self.assertEqual(len(response.data['items']), 2)
self.assertEqual(response.data['items'][0]['id'], x1.pk) self.assertEqual(response.data['items'][0]['id'], x1.pk)
self.assertEqual(response.data['items'][0]['parse']['status'], 'verified') self.assertEqual(response.data['items'][0]['parse']['status'], 'verified')
@ -115,13 +111,20 @@ class TestRSFormViewset(EndpointTester):
self.assertEqual(response.data['subscribers'], [self.user.pk]) self.assertEqual(response.data['subscribers'], [self.user.pk])
self.assertEqual(response.data['editors'], []) self.assertEqual(response.data['editors'], [])
self.executeOK(item=self.unowned_id)
self.executeForbidden(item=self.private_id)
self.logout()
self.executeOK(item=self.owned_id)
self.executeOK(item=self.unowned_id)
self.executeForbidden(item=self.private_id)
@decl_endpoint('/api/rsforms/{item}/check', method='post') @decl_endpoint('/api/rsforms/{item}/check', method='post')
def test_check(self): def test_check(self):
schema = RSForm.create(title='Test') self.owned.insert_new('X1')
schema.insert_new('X1')
data = {'expression': 'X1=X1'} data = {'expression': 'X1=X1'}
response = self.executeOK(data, item=schema.item.pk) response = self.executeOK(data, item=self.owned_id)
self.assertEqual(response.data['parseResult'], True) self.assertEqual(response.data['parseResult'], True)
self.assertEqual(response.data['syntax'], 'math') self.assertEqual(response.data['syntax'], 'math')
self.assertEqual(response.data['astText'], '[=[X1][X1]]') self.assertEqual(response.data['astText'], '[=[X1][X1]]')
@ -133,14 +136,13 @@ class TestRSFormViewset(EndpointTester):
@decl_endpoint('/api/rsforms/{item}/resolve', method='post') @decl_endpoint('/api/rsforms/{item}/resolve', method='post')
def test_resolve(self): def test_resolve(self):
schema = RSForm.create(title='Test') x1 = self.owned.insert_new(
x1 = schema.insert_new(
alias='X1', alias='X1',
term_resolved='синий слон' term_resolved='синий слон'
) )
data = {'text': '@{1|редкий} @{X1|plur,datv}'} data = {'text': '@{1|редкий} @{X1|plur,datv}'}
response = self.executeOK(data, item=schema.item.pk) response = self.executeOK(data, item=self.owned_id)
self.assertEqual(response.data['input'], '@{1|редкий} @{X1|plur,datv}') self.assertEqual(response.data['input'], '@{1|редкий} @{X1|plur,datv}')
self.assertEqual(response.data['output'], 'редким синим слонам') self.assertEqual(response.data['output'], 'редким синим слонам')
self.assertEqual(len(response.data['refs']), 2) self.assertEqual(len(response.data['refs']), 2)
@ -190,10 +192,10 @@ class TestRSFormViewset(EndpointTester):
data = {'alias': 'X3', 'cst_type': CstType.BASE} data = {'alias': 'X3', 'cst_type': CstType.BASE}
self.executeForbidden(data, item=self.unowned_id) self.executeForbidden(data, item=self.unowned_id)
self.schema.insert_new('X1') self.owned.insert_new('X1')
x2 = self.schema.insert_new('X2') x2 = self.owned.insert_new('X2')
response = self.executeCreated(data, item=self.schema_id) response = self.executeCreated(data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], 'X3') self.assertEqual(response.data['new_cst']['alias'], 'X3')
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias']) x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x3.order, 3) self.assertEqual(x3.order, 3)
@ -205,7 +207,7 @@ class TestRSFormViewset(EndpointTester):
'term_raw': 'test', 'term_raw': 'test',
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}] 'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}]
} }
response = self.executeCreated(data, item=self.schema_id) response = self.executeCreated(data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], data['alias']) self.assertEqual(response.data['new_cst']['alias'], data['alias'])
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias']) x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x4.order, 3) self.assertEqual(x4.order, 3)
@ -215,7 +217,7 @@ class TestRSFormViewset(EndpointTester):
@decl_endpoint('/api/rsforms/{item}/cst-rename', method='patch') @decl_endpoint('/api/rsforms/{item}/cst-rename', method='patch')
def test_rename_constituenta(self): def test_rename_constituenta(self):
x1 = self.schema.insert_new( x1 = self.owned.insert_new(
alias='X1', alias='X1',
convention='Test', convention='Test',
term_raw='Test1', term_raw='Test1',
@ -223,7 +225,7 @@ class TestRSFormViewset(EndpointTester):
term_forms=[{'text': 'form1', 'tags': 'sing,datv'}] term_forms=[{'text': 'form1', 'tags': 'sing,datv'}]
) )
x2_2 = self.unowned.insert_new('X2') x2_2 = self.unowned.insert_new('X2')
x3 = self.schema.insert_new( x3 = self.owned.insert_new(
alias='X3', alias='X3',
term_raw='Test3', term_raw='Test3',
term_resolved='Test3', term_resolved='Test3',
@ -233,15 +235,15 @@ class TestRSFormViewset(EndpointTester):
data = {'target': x2_2.pk, 'alias': 'D2', 'cst_type': CstType.TERM} data = {'target': x2_2.pk, 'alias': 'D2', 'cst_type': CstType.TERM}
self.executeForbidden(data, item=self.unowned_id) self.executeForbidden(data, item=self.unowned_id)
self.executeBadData(data, item=self.schema_id) self.executeBadData(data, item=self.owned_id)
data = {'target': x1.pk, 'alias': x1.alias, 'cst_type': CstType.TERM} data = {'target': x1.pk, 'alias': x1.alias, 'cst_type': CstType.TERM}
self.executeBadData(data, item=self.schema_id) self.executeBadData(data, item=self.owned_id)
data = {'target': x1.pk, 'alias': x3.alias} data = {'target': x1.pk, 'alias': x3.alias}
self.executeBadData(data, item=self.schema_id) self.executeBadData(data, item=self.owned_id)
d1 = self.schema.insert_new( d1 = self.owned.insert_new(
alias='D1', alias='D1',
term_raw='@{X1|plur}', term_raw='@{X1|plur}',
definition_formal='X1' definition_formal='X1'
@ -251,7 +253,7 @@ class TestRSFormViewset(EndpointTester):
self.assertEqual(x1.cst_type, CstType.BASE) self.assertEqual(x1.cst_type, CstType.BASE)
data = {'target': x1.pk, 'alias': 'D2', 'cst_type': CstType.TERM} data = {'target': x1.pk, 'alias': 'D2', 'cst_type': CstType.TERM}
response = self.executeOK(data, item=self.schema_id) response = self.executeOK(data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], 'D2') self.assertEqual(response.data['new_cst']['alias'], 'D2')
self.assertEqual(response.data['new_cst']['cst_type'], CstType.TERM) self.assertEqual(response.data['new_cst']['cst_type'], CstType.TERM)
d1.refresh_from_db() d1.refresh_from_db()
@ -265,13 +267,13 @@ class TestRSFormViewset(EndpointTester):
@decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch') @decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch')
def test_substitute_single(self): def test_substitute_single(self):
x1 = self.schema.insert_new( x1 = self.owned.insert_new(
alias='X1', alias='X1',
term_raw='Test1', term_raw='Test1',
term_resolved='Test1', term_resolved='Test1',
term_forms=[{'text': 'form1', 'tags': 'sing,datv'}] term_forms=[{'text': 'form1', 'tags': 'sing,datv'}]
) )
x2 = self.schema.insert_new( x2 = self.owned.insert_new(
alias='X2', alias='X2',
term_raw='Test2' term_raw='Test2'
) )
@ -279,21 +281,21 @@ class TestRSFormViewset(EndpointTester):
data = {'substitutions': [{'original': x1.pk, 'substitution': unowned.pk, 'transfer_term': True}]} data = {'substitutions': [{'original': x1.pk, 'substitution': unowned.pk, 'transfer_term': True}]}
self.executeForbidden(data, item=self.unowned_id) self.executeForbidden(data, item=self.unowned_id)
self.executeBadData(data, item=self.schema_id) self.executeBadData(data, item=self.owned_id)
data = {'substitutions': [{'original': unowned.pk, 'substitution': x1.pk, 'transfer_term': True}]} data = {'substitutions': [{'original': unowned.pk, 'substitution': x1.pk, 'transfer_term': True}]}
self.executeBadData(data, item=self.schema_id) self.executeBadData(data, item=self.owned_id)
data = {'substitutions': [{'original': x1.pk, 'substitution': x1.pk, 'transfer_term': True}]} data = {'substitutions': [{'original': x1.pk, 'substitution': x1.pk, 'transfer_term': True}]}
self.executeBadData(data, item=self.schema_id) self.executeBadData(data, item=self.owned_id)
d1 = self.schema.insert_new( d1 = self.owned.insert_new(
alias='D1', alias='D1',
term_raw='@{X2|sing,datv}', term_raw='@{X2|sing,datv}',
definition_formal='X1' definition_formal='X1'
) )
data = {'substitutions': [{'original': x1.pk, 'substitution': x2.pk, 'transfer_term': True}]} data = {'substitutions': [{'original': x1.pk, 'substitution': x2.pk, 'transfer_term': True}]}
response = self.executeOK(data, item=self.schema_id) response = self.executeOK(data, item=self.owned_id)
d1.refresh_from_db() d1.refresh_from_db()
x2.refresh_from_db() x2.refresh_from_db()
self.assertEqual(x2.term_raw, 'Test1') self.assertEqual(x2.term_raw, 'Test1')
@ -302,12 +304,12 @@ class TestRSFormViewset(EndpointTester):
@decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch') @decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch')
def test_substitute_multiple(self): def test_substitute_multiple(self):
self.set_params(item=self.schema_id) self.set_params(item=self.owned_id)
x1 = self.schema.insert_new('X1') x1 = self.owned.insert_new('X1')
x2 = self.schema.insert_new('X2') x2 = self.owned.insert_new('X2')
d1 = self.schema.insert_new('D1') d1 = self.owned.insert_new('D1')
d2 = self.schema.insert_new('D2') d2 = self.owned.insert_new('D2')
d3 = self.schema.insert_new( d3 = self.owned.insert_new(
alias='D3', alias='D3',
definition_formal=r'X1 \ X2' definition_formal=r'X1 \ X2'
) )
@ -341,7 +343,7 @@ class TestRSFormViewset(EndpointTester):
'transfer_term': True 'transfer_term': True
} }
]} ]}
response = self.executeOK(data, item=self.schema_id) response = self.executeOK(data, item=self.owned_id)
d3.refresh_from_db() d3.refresh_from_db()
self.assertEqual(d3.definition_formal, r'D1 \ D2') self.assertEqual(d3.definition_formal, r'D1 \ D2')
@ -356,7 +358,7 @@ class TestRSFormViewset(EndpointTester):
'definition_formal': '3', 'definition_formal': '3',
'definition_raw': '4' 'definition_raw': '4'
} }
response = self.executeCreated(data, item=self.schema_id) response = self.executeCreated(data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], 'X3') self.assertEqual(response.data['new_cst']['alias'], 'X3')
self.assertEqual(response.data['new_cst']['cst_type'], CstType.BASE) self.assertEqual(response.data['new_cst']['cst_type'], CstType.BASE)
self.assertEqual(response.data['new_cst']['convention'], '1') self.assertEqual(response.data['new_cst']['convention'], '1')
@ -369,43 +371,43 @@ class TestRSFormViewset(EndpointTester):
@decl_endpoint('/api/rsforms/{item}/cst-delete-multiple', method='patch') @decl_endpoint('/api/rsforms/{item}/cst-delete-multiple', method='patch')
def test_delete_constituenta(self): def test_delete_constituenta(self):
self.set_params(item=self.schema_id) self.set_params(item=self.owned_id)
data = {'items': [1337]} data = {'items': [1337]}
self.executeBadData(data) self.executeBadData(data)
x1 = self.schema.insert_new('X1') x1 = self.owned.insert_new('X1')
x2 = self.schema.insert_new('X2') x2 = self.owned.insert_new('X2')
data = {'items': [x1.pk]} data = {'items': [x1.pk]}
response = self.executeOK(data) response = self.executeOK(data)
x2.refresh_from_db() x2.refresh_from_db()
self.schema.item.refresh_from_db() self.owned.item.refresh_from_db()
self.assertEqual(len(response.data['items']), 1) self.assertEqual(len(response.data['items']), 1)
self.assertEqual(self.schema.constituents().count(), 1) self.assertEqual(self.owned.constituents().count(), 1)
self.assertEqual(x2.alias, 'X2') self.assertEqual(x2.alias, 'X2')
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 1)
x3 = self.unowned.insert_new('X1') x3 = self.unowned.insert_new('X1')
data = {'items': [x3.pk]} data = {'items': [x3.pk]}
self.executeBadData(data, item=self.schema_id) self.executeBadData(data, item=self.owned_id)
@decl_endpoint('/api/rsforms/{item}/cst-moveto', method='patch') @decl_endpoint('/api/rsforms/{item}/cst-moveto', method='patch')
def test_move_constituenta(self): def test_move_constituenta(self):
self.set_params(item=self.schema_id) self.set_params(item=self.owned_id)
data = {'items': [1337], 'move_to': 1} data = {'items': [1337], 'move_to': 1}
self.executeBadData(data) self.executeBadData(data)
x1 = self.schema.insert_new('X1') x1 = self.owned.insert_new('X1')
x2 = self.schema.insert_new('X2') x2 = self.owned.insert_new('X2')
data = {'items': [x2.pk], 'move_to': 1} data = {'items': [x2.pk], 'move_to': 1}
response = self.executeOK(data) response = self.executeOK(data)
x1.refresh_from_db() x1.refresh_from_db()
x2.refresh_from_db() x2.refresh_from_db()
self.assertEqual(response.data['id'], self.schema_id) self.assertEqual(response.data['id'], self.owned_id)
self.assertEqual(x1.order, 2) self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 1)
@ -416,14 +418,14 @@ class TestRSFormViewset(EndpointTester):
@decl_endpoint('/api/rsforms/{item}/reset-aliases', method='patch') @decl_endpoint('/api/rsforms/{item}/reset-aliases', method='patch')
def test_reset_aliases(self): def test_reset_aliases(self):
self.set_params(item=self.schema_id) self.set_params(item=self.owned_id)
response = self.executeOK() response = self.executeOK()
self.assertEqual(response.data['id'], self.schema_id) self.assertEqual(response.data['id'], self.owned_id)
x2 = self.schema.insert_new('X2') x2 = self.owned.insert_new('X2')
x1 = self.schema.insert_new('X1') x1 = self.owned.insert_new('X1')
d11 = self.schema.insert_new('D11') d11 = self.owned.insert_new('D11')
response = self.executeOK() response = self.executeOK()
x1.refresh_from_db() x1.refresh_from_db()
@ -441,43 +443,43 @@ class TestRSFormViewset(EndpointTester):
@decl_endpoint('/api/rsforms/{item}/load-trs', method='patch') @decl_endpoint('/api/rsforms/{item}/load-trs', method='patch')
def test_load_trs(self): def test_load_trs(self):
self.set_params(item=self.schema_id) self.set_params(item=self.owned_id)
self.schema.item.title = 'Test11' self.owned.item.title = 'Test11'
self.schema.item.save() self.owned.item.save()
x1 = self.schema.insert_new('X1') x1 = self.owned.insert_new('X1')
work_dir = os.path.dirname(os.path.abspath(__file__)) work_dir = os.path.dirname(os.path.abspath(__file__))
with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file: with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file:
data = {'file': file, 'load_metadata': False} data = {'file': file, 'load_metadata': False}
response = self.client.patch(self.endpoint, data=data, format='multipart') response = self.client.patch(self.endpoint, data=data, format='multipart')
self.schema.item.refresh_from_db() self.owned.item.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.schema.item.title, 'Test11') self.assertEqual(self.owned.item.title, 'Test11')
self.assertEqual(len(response.data['items']), 25) self.assertEqual(len(response.data['items']), 25)
self.assertEqual(self.schema.constituents().count(), 25) self.assertEqual(self.owned.constituents().count(), 25)
self.assertFalse(Constituenta.objects.filter(pk=x1.pk).exists()) self.assertFalse(Constituenta.objects.filter(pk=x1.pk).exists())
@decl_endpoint('/api/rsforms/{item}/cst-produce-structure', method='patch') @decl_endpoint('/api/rsforms/{item}/cst-produce-structure', method='patch')
def test_produce_structure(self): def test_produce_structure(self):
self.set_params(item=self.schema_id) self.set_params(item=self.owned_id)
x1 = self.schema.insert_new('X1') x1 = self.owned.insert_new('X1')
s1 = self.schema.insert_new( s1 = self.owned.insert_new(
alias='S1', alias='S1',
definition_formal='(X1×X1)' definition_formal='(X1×X1)'
) )
s2 = self.schema.insert_new( s2 = self.owned.insert_new(
alias='S2', alias='S2',
definition_formal='invalid' definition_formal='invalid'
) )
s3 = self.schema.insert_new( s3 = self.owned.insert_new(
alias='S3', alias='S3',
definition_formal='X1×(X1×(X1))×(X1×X1)' definition_formal='X1×(X1×(X1))×(X1×X1)'
) )
a1 = self.schema.insert_new( a1 = self.owned.insert_new(
alias='A1', alias='A1',
definition_formal='1=1' definition_formal='1=1'
) )
f1 = self.schema.insert_new( f1 = self.owned.insert_new(
alias='F10', alias='F10',
definition_formal='[α∈X1, β∈X1] Fi1[{α,β}](S1)' definition_formal='[α∈X1, β∈X1] Fi1[{α,β}](S1)'
) )

View File

@ -18,74 +18,22 @@ from .. import permissions
from .. import serializers as s from .. import serializers as s
@extend_schema(tags=['Library'])
@extend_schema_view()
class LibraryActiveView(generics.ListAPIView):
''' Endpoint: Get list of library items available for active user. '''
permission_classes = (permissions.Anyone,)
serializer_class = s.LibraryItemSerializer
def get_queryset(self):
if self.request.user.is_anonymous:
return m.LibraryItem.objects.filter(
Q(access_policy=m.AccessPolicy.PUBLIC),
).filter(
Q(location__startswith=m.LocationHead.COMMON) |
Q(location__startswith=m.LocationHead.LIBRARY)
).order_by('-time_update')
else:
user = cast(m.User, self.request.user)
# pylint: disable=unsupported-binary-operation
return m.LibraryItem.objects.filter(
(
Q(access_policy=m.AccessPolicy.PUBLIC) &
(
Q(location__startswith=m.LocationHead.COMMON) |
Q(location__startswith=m.LocationHead.LIBRARY)
)
) |
Q(owner=user) |
Q(editor__editor=user) |
Q(subscription__user=user)
).distinct().order_by('-time_update')
@extend_schema(tags=['Library'])
@extend_schema_view()
class LibraryAdminView(generics.ListAPIView):
''' Endpoint: Get list of all library items. Admin only '''
permission_classes = (permissions.GlobalAdmin,)
serializer_class = s.LibraryItemSerializer
def get_queryset(self):
return m.LibraryItem.objects.all().order_by('-time_update')
@extend_schema(tags=['Library'])
@extend_schema_view()
class LibraryTemplatesView(generics.ListAPIView):
''' Endpoint: Get list of templates. '''
permission_classes = (permissions.Anyone,)
serializer_class = s.LibraryItemSerializer
def get_queryset(self):
template_ids = m.LibraryTemplate.objects.values_list('lib_source', flat=True)
return m.LibraryItem.objects.filter(pk__in=template_ids)
# pylint: disable=too-many-ancestors
@extend_schema(tags=['Library']) @extend_schema(tags=['Library'])
@extend_schema_view() @extend_schema_view()
class LibraryViewSet(viewsets.ModelViewSet): class LibraryViewSet(viewsets.ModelViewSet):
''' Endpoint: Library operations. ''' ''' Endpoint: Library operations. '''
queryset = m.LibraryItem.objects.all() queryset = m.LibraryItem.objects.all()
serializer_class = s.LibraryItemSerializer
filter_backends = (DjangoFilterBackend, filters.OrderingFilter) filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
filterset_fields = ['item_type', 'owner'] filterset_fields = ['item_type', 'owner']
ordering_fields = ('item_type', 'owner', 'alias', 'title', 'time_update') ordering_fields = ('item_type', 'owner', 'alias', 'title', 'time_update')
ordering = '-time_update' ordering = '-time_update'
def get_serializer_class(self):
if self.action == 'create':
return s.LibraryItemBaseSerializer
return s.LibraryItemSerializer
def perform_create(self, serializer): def perform_create(self, serializer):
if not self.request.user.is_anonymous and 'owner' not in self.request.POST: if not self.request.user.is_anonymous and 'owner' not in self.request.POST:
return serializer.save(owner=self.request.user) return serializer.save(owner=self.request.user)
@ -103,7 +51,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
elif self.action in ['create', 'clone', 'subscribe', 'unsubscribe']: elif self.action in ['create', 'clone', 'subscribe', 'unsubscribe']:
permission_list = [permissions.GlobalUser] permission_list = [permissions.GlobalUser]
else: else:
permission_list = [permissions.Anyone] permission_list = [permissions.ItemAnyone]
return [permission() for permission in permission_list] return [permission() for permission in permission_list]
def _get_item(self) -> m.LibraryItem: def _get_item(self) -> m.LibraryItem:
@ -308,3 +256,58 @@ class LibraryViewSet(viewsets.ModelViewSet):
editors = serializer.validated_data['users'] editors = serializer.validated_data['users']
m.Editor.set(item=item, users=editors) m.Editor.set(item=item, users=editors)
return Response(status=c.HTTP_200_OK) return Response(status=c.HTTP_200_OK)
@extend_schema(tags=['Library'])
@extend_schema_view()
class LibraryActiveView(generics.ListAPIView):
''' Endpoint: Get list of library items available for active user. '''
permission_classes = (permissions.Anyone,)
serializer_class = s.LibraryItemSerializer
def get_queryset(self):
if self.request.user.is_anonymous:
return m.LibraryItem.objects.filter(
Q(access_policy=m.AccessPolicy.PUBLIC),
).filter(
Q(location__startswith=m.LocationHead.COMMON) |
Q(location__startswith=m.LocationHead.LIBRARY)
).order_by('-time_update')
else:
user = cast(m.User, self.request.user)
# pylint: disable=unsupported-binary-operation
return m.LibraryItem.objects.filter(
(
Q(access_policy=m.AccessPolicy.PUBLIC) &
(
Q(location__startswith=m.LocationHead.COMMON) |
Q(location__startswith=m.LocationHead.LIBRARY)
)
) |
Q(owner=user) |
Q(editor__editor=user) |
Q(subscription__user=user)
).distinct().order_by('-time_update')
@extend_schema(tags=['Library'])
@extend_schema_view()
class LibraryAdminView(generics.ListAPIView):
''' Endpoint: Get list of all library items. Admin only '''
permission_classes = (permissions.GlobalAdmin,)
serializer_class = s.LibraryItemSerializer
def get_queryset(self):
return m.LibraryItem.objects.all().order_by('-time_update')
@extend_schema(tags=['Library'])
@extend_schema_view()
class LibraryTemplatesView(generics.ListAPIView):
''' Endpoint: Get list of templates. '''
permission_classes = (permissions.Anyone,)
serializer_class = s.LibraryItemSerializer
def get_queryset(self):
template_ids = m.LibraryTemplate.objects.values_list('lib_source', flat=True)
return m.LibraryItem.objects.filter(pk__in=template_ids)

View File

@ -441,6 +441,7 @@ class TrsImportView(views.APIView):
request=s.LibraryItemSerializer, request=s.LibraryItemSerializer,
responses={ responses={
c.HTTP_201_CREATED: s.LibraryItemSerializer, c.HTTP_201_CREATED: s.LibraryItemSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None c.HTTP_403_FORBIDDEN: None
} }
) )
@ -449,17 +450,9 @@ def create_rsform(request: Request):
''' Endpoint: Create RSForm from user input and/or trs file. ''' ''' Endpoint: Create RSForm from user input and/or trs file. '''
owner = cast(m.User, request.user) if not request.user.is_anonymous else None owner = cast(m.User, request.user) if not request.user.is_anonymous else None
if 'file' not in request.FILES: if 'file' not in request.FILES:
serializer = s.LibraryItemBase(data=request.data) return Response(
serializer.is_valid(raise_exception=True) status=c.HTTP_400_BAD_REQUEST,
schema = m.RSForm.create( data={f'file': msg.missingFile()}
title=serializer.validated_data['title'],
owner=owner,
alias=serializer.validated_data.get('alias', ''),
comment=serializer.validated_data.get('comment', ''),
visible=serializer.validated_data.get('visible', True),
read_only=serializer.validated_data.get('read_only', False),
access_policy=serializer.validated_data.get('access_policy', m.AccessPolicy.PUBLIC),
location=serializer.validated_data.get('location', m.LocationHead.USER),
) )
else: else:
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME) data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)

View File

@ -1,6 +1,6 @@
import { createBrowserRouter } from 'react-router-dom'; import { createBrowserRouter } from 'react-router-dom';
import CreateItemPage from '@/pages/CreateRSFormPage'; import CreateItemPage from '@/pages/CreateItemPage';
import HomePage from '@/pages/HomePage'; import HomePage from '@/pages/HomePage';
import LibraryPage from '@/pages/LibraryPage'; import LibraryPage from '@/pages/LibraryPage';
import LoginPage from '@/pages/LoginPage'; import LoginPage from '@/pages/LoginPage';

View File

@ -198,7 +198,7 @@ export function getTemplates(request: FrontPull<ILibraryItem[]>) {
}); });
} }
export function postNewRSForm(request: FrontExchange<ILibraryCreateData, ILibraryItem>) { export function postRSFormFromFile(request: FrontExchange<ILibraryCreateData, ILibraryItem>) {
AxiosPost({ AxiosPost({
endpoint: '/api/rsforms/create-detailed', endpoint: '/api/rsforms/create-detailed',
request: request, request: request,
@ -210,6 +210,13 @@ export function postNewRSForm(request: FrontExchange<ILibraryCreateData, ILibrar
}); });
} }
export function postCreateLibraryItem(request: FrontExchange<ILibraryCreateData, ILibraryItem>) {
AxiosPost({
endpoint: '/api/library',
request: request
});
}
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCloneData, IRSFormData>) { export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCloneData, IRSFormData>) {
AxiosPost({ AxiosPost({
endpoint: `/api/library/${target}/clone`, endpoint: `/api/library/${target}/clone`,

View File

@ -44,6 +44,7 @@ export const urls = {
help_topic: (topic: string) => `/manuals?topic=${topic}`, help_topic: (topic: string) => `/manuals?topic=${topic}`,
schema: (id: number | string, version?: number | string) => schema: (id: number | string, version?: number | string) =>
`/rsforms/${id}` + (version !== undefined ? `?v=${version}` : ''), `/rsforms/${id}` + (version !== undefined ? `?v=${version}` : ''),
oss: (id: number | string) => `/oss/${id}`,
schema_props: ({ id, tab, version, active }: SchemaProps) => { schema_props: ({ id, tab, version, active }: SchemaProps) => {
const versionStr = version !== undefined ? `v=${version}&` : ''; const versionStr = version !== undefined ? `v=${version}&` : '';
const activeStr = active !== undefined ? `&active=${active}` : ''; const activeStr = active !== undefined ? `&active=${active}` : '';

View File

@ -1,4 +1,4 @@
import { AccessPolicy, LocationHead } from '@/models/library'; import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
import { CstMatchMode, DependencyMode } from '@/models/miscellaneous'; import { CstMatchMode, DependencyMode } from '@/models/miscellaneous';
import { import {
@ -13,10 +13,12 @@ import {
IconGraphInputs, IconGraphInputs,
IconGraphOutputs, IconGraphOutputs,
IconHide, IconHide,
IconOSS,
IconPrivate, IconPrivate,
IconProps, IconProps,
IconProtected, IconProtected,
IconPublic, IconPublic,
IconRSForm,
IconSettings, IconSettings,
IconShow, IconShow,
IconTemplates, IconTemplates,
@ -29,6 +31,15 @@ export interface DomIconProps<RequestData> extends IconProps {
value: RequestData; value: RequestData;
} }
export function ItemTypeIcon({ value, size = '1.25rem', className }: DomIconProps<LibraryItemType>) {
switch (value) {
case LibraryItemType.RSFORM:
return <IconRSForm size={size} className={className ?? 'clr-text-primary'} />;
case LibraryItemType.OSS:
return <IconOSS size={size} className={className ?? 'clr-text-green'} />;
}
}
export function PolicyIcon({ value, size = '1.25rem', className }: DomIconProps<AccessPolicy>) { export function PolicyIcon({ value, size = '1.25rem', className }: DomIconProps<AccessPolicy>) {
switch (value) { switch (value) {
case AccessPolicy.PRIVATE: case AccessPolicy.PRIVATE:

View File

@ -56,6 +56,8 @@ export { TbBriefcase as IconBusiness } from 'react-icons/tb';
export { VscLibrary as IconLibrary } from 'react-icons/vsc'; export { VscLibrary as IconLibrary } from 'react-icons/vsc';
export { IoLibrary as IconLibrary2 } from 'react-icons/io5'; export { IoLibrary as IconLibrary2 } from 'react-icons/io5';
export { BiDiamond as IconTemplates } from 'react-icons/bi'; export { BiDiamond as IconTemplates } from 'react-icons/bi';
export { FaRegObjectGroup as IconOSS } from 'react-icons/fa';
export { RiHexagonLine as IconRSForm } from 'react-icons/ri';
export { LuArchive as IconArchive } from 'react-icons/lu'; export { LuArchive as IconArchive } from 'react-icons/lu';
export { LuDatabase as IconDatabase } from 'react-icons/lu'; export { LuDatabase as IconDatabase } from 'react-icons/lu';
export { LuImage as IconImage } from 'react-icons/lu'; export { LuImage as IconImage } from 'react-icons/lu';

View File

@ -0,0 +1,62 @@
'use client';
import { useCallback } from 'react';
import Dropdown from '@/components/ui/Dropdown';
import useDropdown from '@/hooks/useDropdown';
import { LibraryItemType } from '@/models/library';
import { prefixes } from '@/utils/constants';
import { describeLibraryItemType, labelLibraryItemType } from '@/utils/labels';
import { ItemTypeIcon } from '../DomainIcons';
import DropdownButton from '../ui/DropdownButton';
import SelectorButton from '../ui/SelectorButton';
interface SelectItemTypeProps {
value: LibraryItemType;
onChange: (value: LibraryItemType) => void;
disabled?: boolean;
stretchLeft?: boolean;
}
function SelectItemType({ value, disabled, stretchLeft, onChange }: SelectItemTypeProps) {
const menu = useDropdown();
const handleChange = useCallback(
(newValue: LibraryItemType) => {
menu.hide();
if (newValue !== value) {
onChange(newValue);
}
},
[menu, value, onChange]
);
return (
<div ref={menu.ref}>
<SelectorButton
transparent
title={describeLibraryItemType(value)}
hideTitle={menu.isOpen}
className='h-full py-1 px-2 disabled:cursor-auto rounded-lg'
icon={<ItemTypeIcon value={value} size='1.25rem' />}
text={labelLibraryItemType(value)}
onClick={menu.toggle}
disabled={disabled}
/>
<Dropdown isOpen={menu.isOpen} stretchLeft={stretchLeft}>
{Object.values(LibraryItemType).map((item, index) => (
<DropdownButton
key={`${prefixes.policy_list}${index}`}
text={labelLibraryItemType(item)}
title={describeLibraryItemType(item)}
icon={<ItemTypeIcon value={item} size='1rem' />}
onClick={() => handleChange(item)}
/>
))}
</Dropdown>
</div>
);
}
export default SelectItemType;

View File

@ -34,7 +34,6 @@ function SelectMatchMode({ value, onChange }: SelectMatchModeProps) {
<div ref={menu.ref}> <div ref={menu.ref}>
<SelectorButton <SelectorButton
transparent transparent
tabIndex={-1}
title='Настройка фильтрации по проверяемым атрибутам' title='Настройка фильтрации по проверяемым атрибутам'
hideTitle={menu.isOpen} hideTitle={menu.isOpen}
className='h-full pr-2' className='h-full pr-2'

View File

@ -26,6 +26,7 @@ function SelectorButton({
return ( return (
<button <button
type='button' type='button'
tabIndex={-1}
className={clsx( className={clsx(
'px-1 flex flex-start items-center gap-1', 'px-1 flex flex-start items-center gap-1',
'text-sm font-controls select-none', 'text-sm font-controls select-none',

View File

@ -10,7 +10,8 @@ import {
getRSFormDetails, getRSFormDetails,
getTemplates, getTemplates,
postCloneLibraryItem, postCloneLibraryItem,
postNewRSForm postCreateLibraryItem,
postRSFormFromFile
} from '@/app/backendAPI'; } from '@/app/backendAPI';
import { ErrorData } from '@/components/info/InfoError'; import { ErrorData } from '@/components/info/InfoError';
import { ILibraryItem, LibraryItemID } from '@/models/library'; import { ILibraryItem, LibraryItemID } from '@/models/library';
@ -181,20 +182,31 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
const createItem = useCallback( const createItem = useCallback(
(data: ILibraryCreateData, callback?: DataCallback<ILibraryItem>) => { (data: ILibraryCreateData, callback?: DataCallback<ILibraryItem>) => {
setError(undefined); const onSuccess = (newSchema: ILibraryItem) =>
postNewRSForm({
data: data,
showError: true,
setLoading: setProcessing,
onError: setError,
onSuccess: newSchema =>
reloadItems(() => { reloadItems(() => {
if (user && !user.subscriptions.includes(newSchema.id)) { if (user && !user.subscriptions.includes(newSchema.id)) {
user.subscriptions.push(newSchema.id); user.subscriptions.push(newSchema.id);
} }
if (callback) callback(newSchema); if (callback) callback(newSchema);
})
}); });
setError(undefined);
if (data.file) {
postRSFormFromFile({
data: data,
showError: true,
setLoading: setProcessing,
onError: setError,
onSuccess: onSuccess
});
} else {
postCreateLibraryItem({
data: data,
showError: true,
setLoading: setProcessing,
onError: setError,
onSuccess: onSuccess
});
}
}, },
[reloadItems, user] [reloadItems, user]
); );

View File

@ -9,7 +9,7 @@ import { UserID } from './user';
*/ */
export enum LibraryItemType { export enum LibraryItemType {
RSFORM = 'rsform', RSFORM = 'rsform',
OPERATIONS_SCHEMA = 'oss' OSS = 'oss'
} }
/** /**

View File

@ -9,6 +9,7 @@ import { VisibilityIcon } from '@/components/DomainIcons';
import { IconDownload } from '@/components/Icons'; import { IconDownload } from '@/components/Icons';
import InfoError from '@/components/info/InfoError'; import InfoError from '@/components/info/InfoError';
import SelectAccessPolicy from '@/components/select/SelectAccessPolicy'; import SelectAccessPolicy from '@/components/select/SelectAccessPolicy';
import SelectItemType from '@/components/select/SelectItemType';
import SelectLocationHead from '@/components/select/SelectLocationHead'; import SelectLocationHead from '@/components/select/SelectLocationHead';
import Button from '@/components/ui/Button'; import Button from '@/components/ui/Button';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
@ -30,6 +31,7 @@ function FormCreateItem() {
const { user } = useAuth(); const { user } = useAuth();
const { createItem, error, setError, processing } = useLibrary(); const { createItem, error, setError, processing } = useLibrary();
const [itemType, setItemType] = useState(LibraryItemType.RSFORM);
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [alias, setAlias] = useState(''); const [alias, setAlias] = useState('');
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
@ -64,7 +66,7 @@ function FormCreateItem() {
return; return;
} }
const data: ILibraryCreateData = { const data: ILibraryCreateData = {
item_type: LibraryItemType.RSFORM, item_type: itemType,
title: title, title: title,
alias: alias, alias: alias,
comment: comment, comment: comment,
@ -75,9 +77,13 @@ function FormCreateItem() {
file: file, file: file,
fileName: file?.name fileName: file?.name
}; };
createItem(data, newSchema => { createItem(data, newItem => {
toast.success('Схема успешно создана'); toast.success('Схема успешно создана');
router.push(urls.schema(newSchema.id)); if (itemType == LibraryItemType.RSFORM) {
router.push(urls.schema(newItem.id));
} else {
router.push(urls.oss(newItem.id));
}
}); });
} }
@ -109,8 +115,8 @@ function FormCreateItem() {
/> />
</Overlay> </Overlay>
<form className={clsx('cc-column', 'min-w-[30rem]', 'px-6 py-3')} onSubmit={handleSubmit}> <form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem]', 'px-6 py-3')} onSubmit={handleSubmit}>
<h1>Создание концептуальной схемы</h1> <h1>Создание схемы</h1>
{fileName ? <Label text={`Загружен файл: ${fileName}`} /> : null} {fileName ? <Label text={`Загружен файл: ${fileName}`} /> : null}
@ -135,11 +141,14 @@ function FormCreateItem() {
value={alias} value={alias}
onChange={event => setAlias(event.target.value)} onChange={event => setAlias(event.target.value)}
/> />
<div className='flex flex-col items-center gap-2'>
<Label text='Тип схемы' className='self-center select-none' />
<SelectItemType value={itemType} onChange={setItemType} />
</div>
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
<Label text='Доступ' className='self-center select-none' /> <Label text='Доступ' className='self-center select-none' />
<div className='ml-auto cc-icons'> <div className='ml-auto cc-icons'>
<SelectAccessPolicy value={policy} onChange={newPolicy => setPolicy(newPolicy)} /> <SelectAccessPolicy value={policy} onChange={setPolicy} />
<MiniButton <MiniButton
className='disabled:cursor-auto' className='disabled:cursor-auto'
title={visible ? 'Библиотека: отображать' : 'Библиотека: скрывать'} title={visible ? 'Библиотека: отображать' : 'Библиотека: скрывать'}

View File

@ -2,7 +2,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import Divider from '@/components/ui/Divider';
import FlexColumn from '@/components/ui/FlexColumn'; import FlexColumn from '@/components/ui/FlexColumn';
import AnimateFade from '@/components/wrap/AnimateFade'; import AnimateFade from '@/components/wrap/AnimateFade';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
@ -52,9 +51,6 @@ function EditorRSForm({ isModified, onDestroy, setIsModified }: EditorRSFormProp
<AnimateFade onKeyDown={handleInput} className={clsx('sm:w-fit mx-auto', 'flex flex-col sm:flex-row')}> <AnimateFade onKeyDown={handleInput} className={clsx('sm:w-fit mx-auto', 'flex flex-col sm:flex-row')}>
<FlexColumn className='px-3'> <FlexColumn className='px-3'>
<FormRSForm id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} /> <FormRSForm id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />
<Divider margins='my-1' />
<EditorLibraryItem item={schema} isModified={isModified} /> <EditorLibraryItem item={schema} isModified={isModified} />
</FlexColumn> </FlexColumn>

View File

@ -6,7 +6,7 @@
*/ */
import { GraphLayout } from '@/components/ui/GraphUI'; import { GraphLayout } from '@/components/ui/GraphUI';
import { GramData, Grammeme, ReferenceType } from '@/models/language'; import { GramData, Grammeme, ReferenceType } from '@/models/language';
import { AccessPolicy, LocationHead } from '@/models/library'; import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
import { CstMatchMode, DependencyMode, GraphColoring, GraphSizing, HelpTopic } from '@/models/miscellaneous'; import { CstMatchMode, DependencyMode, GraphColoring, GraphSizing, HelpTopic } from '@/models/miscellaneous';
import { CstClass, CstType, ExpressionStatus, IConstituenta, IRSForm } from '@/models/rsform'; import { CstClass, CstType, ExpressionStatus, IConstituenta, IRSForm } from '@/models/rsform';
import { import {
@ -821,6 +821,28 @@ export function describeAccessPolicy(policy: AccessPolicy): string {
} }
} }
/**
* Retrieves label for {@link LibraryItemType}.
*/
export function labelLibraryItemType(itemType: LibraryItemType): string {
// prettier-ignore
switch (itemType) {
case LibraryItemType.RSFORM: return 'КС';
case LibraryItemType.OSS: return 'ОСС';
}
}
/**
* Retrieves description for {@link LibraryItemType}.
*/
export function describeLibraryItemType(itemType: LibraryItemType): string {
// prettier-ignore
switch (itemType) {
case LibraryItemType.RSFORM: return 'Концептуальная схема';
case LibraryItemType.OSS: return 'Операционная схема синтеза';
}
}
/** /**
* UI shared messages. * UI shared messages.
*/ */