mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
F: Implement RSForm and OSS attribute sync
This commit is contained in:
parent
e059dc2394
commit
b929b051cc
|
@ -94,9 +94,9 @@ class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class UserTargetSerializer(serializers.Serializer):
|
class UserTargetSerializer(serializers.Serializer):
|
||||||
''' Serializer: Target single User. '''
|
''' Serializer: Target single User. '''
|
||||||
user = PKField(many=False, queryset=User.objects.all())
|
user = PKField(many=False, queryset=User.objects.all().only('pk'))
|
||||||
|
|
||||||
|
|
||||||
class UsersListSerializer(serializers.Serializer):
|
class UsersListSerializer(serializers.Serializer):
|
||||||
''' Serializer: List of Users. '''
|
''' Serializer: List of Users. '''
|
||||||
users = PKField(many=True, queryset=User.objects.all())
|
users = PKField(many=True, queryset=User.objects.all().only('pk'))
|
||||||
|
|
|
@ -183,57 +183,6 @@ class TestLibraryViewset(EndpointTester):
|
||||||
self.unowned.refresh_from_db()
|
self.unowned.refresh_from_db()
|
||||||
self.assertEqual(self.unowned.location, data['location'])
|
self.assertEqual(self.unowned.location, data['location'])
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/add-editor', method='patch')
|
|
||||||
def test_add_editor(self):
|
|
||||||
time_update = self.owned.time_update
|
|
||||||
|
|
||||||
data = {'user': self.invalid_user}
|
|
||||||
self.executeBadData(data=data, item=self.owned.pk)
|
|
||||||
|
|
||||||
data = {'user': self.user.pk}
|
|
||||||
self.executeNotFound(data=data, item=self.invalid_item)
|
|
||||||
self.executeForbidden(data=data, item=self.unowned.pk)
|
|
||||||
|
|
||||||
self.executeOK(data=data, item=self.owned.pk)
|
|
||||||
self.owned.refresh_from_db()
|
|
||||||
self.assertEqual(self.owned.time_update, time_update)
|
|
||||||
self.assertEqual(self.owned.editors(), [self.user])
|
|
||||||
|
|
||||||
self.executeOK(data=data)
|
|
||||||
self.assertEqual(self.owned.editors(), [self.user])
|
|
||||||
|
|
||||||
data = {'user': self.user2.pk}
|
|
||||||
self.executeOK(data=data)
|
|
||||||
self.assertEqual(set(self.owned.editors()), set([self.user, self.user2]))
|
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/remove-editor', method='patch')
|
|
||||||
def test_remove_editor(self):
|
|
||||||
time_update = self.owned.time_update
|
|
||||||
|
|
||||||
data = {'user': self.invalid_user}
|
|
||||||
self.executeBadData(data=data, item=self.owned.pk)
|
|
||||||
|
|
||||||
data = {'user': self.user.pk}
|
|
||||||
self.executeNotFound(data=data, item=self.invalid_item)
|
|
||||||
self.executeForbidden(data=data, item=self.unowned.pk)
|
|
||||||
|
|
||||||
self.executeOK(data=data, item=self.owned.pk)
|
|
||||||
self.owned.refresh_from_db()
|
|
||||||
self.assertEqual(self.owned.time_update, time_update)
|
|
||||||
self.assertEqual(self.owned.editors(), [])
|
|
||||||
|
|
||||||
Editor.add(item=self.owned, user=self.user)
|
|
||||||
self.executeOK(data=data)
|
|
||||||
self.assertEqual(self.owned.editors(), [])
|
|
||||||
|
|
||||||
Editor.add(item=self.owned, user=self.user)
|
|
||||||
Editor.add(item=self.owned, user=self.user2)
|
|
||||||
data = {'user': self.user2.pk}
|
|
||||||
self.executeOK(data=data)
|
|
||||||
self.assertEqual(self.owned.editors(), [self.user])
|
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/set-editors', method='patch')
|
@decl_endpoint('/api/library/{item}/set-editors', method='patch')
|
||||||
def test_set_editors(self):
|
def test_set_editors(self):
|
||||||
time_update = self.owned.time_update
|
time_update = self.owned.time_update
|
||||||
|
|
|
@ -13,6 +13,7 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from apps.oss.models import OperationSchema
|
||||||
from apps.rsform.models import RSForm
|
from apps.rsform.models import RSForm
|
||||||
from apps.rsform.serializers import RSFormParseSerializer
|
from apps.rsform.serializers import RSFormParseSerializer
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
@ -52,8 +53,6 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
'set_owner',
|
'set_owner',
|
||||||
'set_access_policy',
|
'set_access_policy',
|
||||||
'set_location',
|
'set_location',
|
||||||
'add_editor',
|
|
||||||
'remove_editor',
|
|
||||||
'set_editors'
|
'set_editors'
|
||||||
]:
|
]:
|
||||||
access_level = permissions.ItemOwner
|
access_level = permissions.ItemOwner
|
||||||
|
@ -165,29 +164,19 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
serializer = s.UserTargetSerializer(data=request.data)
|
serializer = s.UserTargetSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
new_owner = serializer.validated_data['user']
|
new_owner = serializer.validated_data['user'].pk
|
||||||
m.LibraryItem.objects.filter(pk=item.pk).update(owner=new_owner)
|
if new_owner == item.owner_id:
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
if item.item_type == m.LibraryItemType.OPERATION_SCHEMA:
|
||||||
|
owned_schemas = OperationSchema(item).owned_schemas().only('owner')
|
||||||
|
for schema in owned_schemas:
|
||||||
|
schema.owner_id = new_owner
|
||||||
|
m.LibraryItem.objects.bulk_update(owned_schemas, ['owner'])
|
||||||
|
item.owner_id = new_owner
|
||||||
|
item.save(update_fields=['owner'])
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
summary='set AccessPolicy for item',
|
|
||||||
tags=['Library'],
|
|
||||||
request=s.AccessPolicySerializer,
|
|
||||||
responses={
|
|
||||||
c.HTTP_200_OK: None,
|
|
||||||
c.HTTP_400_BAD_REQUEST: None,
|
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
|
||||||
c.HTTP_404_NOT_FOUND: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@action(detail=True, methods=['patch'], url_path='set-access-policy')
|
|
||||||
def set_access_policy(self, request: Request, pk):
|
|
||||||
''' Endpoint: Set item AccessPolicy. '''
|
|
||||||
item = self._get_item()
|
|
||||||
serializer = s.AccessPolicySerializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
new_policy = serializer.validated_data['access_policy']
|
|
||||||
m.LibraryItem.objects.filter(pk=item.pk).update(access_policy=new_policy)
|
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -208,49 +197,52 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
serializer = s.LocationSerializer(data=request.data)
|
serializer = s.LocationSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
location: str = serializer.validated_data['location']
|
location: str = serializer.validated_data['location']
|
||||||
|
if location == item.location:
|
||||||
|
return Response(status=c.HTTP_200_OK)
|
||||||
if location.startswith(m.LocationHead.LIBRARY) and not self.request.user.is_staff:
|
if location.startswith(m.LocationHead.LIBRARY) and not self.request.user.is_staff:
|
||||||
return Response(status=c.HTTP_403_FORBIDDEN)
|
return Response(status=c.HTTP_403_FORBIDDEN)
|
||||||
m.LibraryItem.objects.filter(pk=item.pk).update(location=location)
|
|
||||||
|
with transaction.atomic():
|
||||||
|
if item.item_type == m.LibraryItemType.OPERATION_SCHEMA:
|
||||||
|
owned_schemas = OperationSchema(item).owned_schemas().only('location')
|
||||||
|
for schema in owned_schemas:
|
||||||
|
schema.location = location
|
||||||
|
m.LibraryItem.objects.bulk_update(owned_schemas, ['location'])
|
||||||
|
item.location = location
|
||||||
|
item.save(update_fields=['location'])
|
||||||
|
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='add editor for item',
|
summary='set AccessPolicy for item',
|
||||||
tags=['Library'],
|
tags=['Library'],
|
||||||
request=s.UserTargetSerializer,
|
request=s.AccessPolicySerializer,
|
||||||
responses={
|
responses={
|
||||||
c.HTTP_200_OK: None,
|
c.HTTP_200_OK: None,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
c.HTTP_404_NOT_FOUND: None
|
c.HTTP_404_NOT_FOUND: None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='add-editor')
|
@action(detail=True, methods=['patch'], url_path='set-access-policy')
|
||||||
def add_editor(self, request: Request, pk):
|
def set_access_policy(self, request: Request, pk):
|
||||||
''' Endpoint: Add editor for item. '''
|
''' Endpoint: Set item AccessPolicy. '''
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
serializer = s.UserTargetSerializer(data=request.data)
|
serializer = s.AccessPolicySerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
new_editor = serializer.validated_data['user']
|
new_policy = serializer.validated_data['access_policy']
|
||||||
m.Editor.add(item=item, user=new_editor)
|
if new_policy == item.access_policy:
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
if item.item_type == m.LibraryItemType.OPERATION_SCHEMA:
|
||||||
|
owned_schemas = OperationSchema(item).owned_schemas().only('access_policy')
|
||||||
|
for schema in owned_schemas:
|
||||||
|
schema.access_policy = new_policy
|
||||||
|
m.LibraryItem.objects.bulk_update(owned_schemas, ['access_policy'])
|
||||||
|
item.access_policy = new_policy
|
||||||
|
item.save(update_fields=['access_policy'])
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
summary='remove editor for item',
|
|
||||||
tags=['Library'],
|
|
||||||
request=s.UserTargetSerializer,
|
|
||||||
responses={
|
|
||||||
c.HTTP_200_OK: None,
|
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
|
||||||
c.HTTP_404_NOT_FOUND: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@action(detail=True, methods=['patch'], url_path='remove-editor')
|
|
||||||
def remove_editor(self, request: Request, pk):
|
|
||||||
''' Endpoint: Remove editor for item. '''
|
|
||||||
item = self._get_item()
|
|
||||||
serializer = s.UserTargetSerializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
editor = serializer.validated_data['user']
|
|
||||||
m.Editor.remove(item=item, user=editor)
|
|
||||||
return Response(status=c.HTTP_200_OK)
|
return Response(status=c.HTTP_200_OK)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
|
|
@ -51,6 +51,14 @@ class OperationSchema:
|
||||||
''' Operation substitutions. '''
|
''' Operation substitutions. '''
|
||||||
return Substitution.objects.filter(operation__oss=self.model)
|
return Substitution.objects.filter(operation__oss=self.model)
|
||||||
|
|
||||||
|
def owned_schemas(self) -> QuerySet[LibraryItem]:
|
||||||
|
''' Get QuerySet containing all result schemas owned by current OSS. '''
|
||||||
|
return LibraryItem.objects.filter(
|
||||||
|
producer__oss=self.model,
|
||||||
|
owner_id=self.model.owner_id,
|
||||||
|
location=self.model.location
|
||||||
|
)
|
||||||
|
|
||||||
def update_positions(self, data: list[dict]):
|
def update_positions(self, data: list[dict]):
|
||||||
''' Update positions. '''
|
''' Update positions. '''
|
||||||
lookup = {x['id']: x for x in data}
|
lookup = {x['id']: x for x in data}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
''' Tests for REST API. '''
|
''' Tests for REST API. '''
|
||||||
|
from .t_change_attributes import *
|
||||||
from .t_oss import *
|
from .t_oss import *
|
||||||
|
|
107
rsconcept/backend/apps/oss/tests/s_views/t_change_attributes.py
Normal file
107
rsconcept/backend/apps/oss/tests/s_views/t_change_attributes.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
''' Testing API: Change attributes of OSS and RSForms. '''
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
from apps.library.models import AccessPolicy, LocationHead
|
||||||
|
from apps.oss.models import Operation, OperationSchema, OperationType
|
||||||
|
from apps.rsform.models import RSForm
|
||||||
|
from apps.users.models import User
|
||||||
|
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||||
|
|
||||||
|
|
||||||
|
class TestChangeAttributes(EndpointTester):
|
||||||
|
''' Testing LibraryItem view when OSS is associated with RSForms. '''
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.user3 = User.objects.create(
|
||||||
|
username='UserTest3',
|
||||||
|
email='anotheranother@test.com',
|
||||||
|
password='password'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.owned = OperationSchema.create(
|
||||||
|
title='Test',
|
||||||
|
alias='T1',
|
||||||
|
owner=self.user,
|
||||||
|
location=LocationHead.LIBRARY
|
||||||
|
)
|
||||||
|
self.owned_id = self.owned.model.pk
|
||||||
|
|
||||||
|
self.ks1 = RSForm.create(
|
||||||
|
alias='KS1',
|
||||||
|
title='Test1',
|
||||||
|
owner=self.user,
|
||||||
|
location=LocationHead.USER
|
||||||
|
)
|
||||||
|
self.ks2 = RSForm.create(
|
||||||
|
alias='KS2',
|
||||||
|
title='Test2',
|
||||||
|
owner=self.user2,
|
||||||
|
location=LocationHead.LIBRARY
|
||||||
|
)
|
||||||
|
|
||||||
|
self.operation1 = self.owned.create_operation(
|
||||||
|
alias='1',
|
||||||
|
operation_type=OperationType.INPUT,
|
||||||
|
result=self.ks1.model
|
||||||
|
)
|
||||||
|
self.operation2 = self.owned.create_operation(
|
||||||
|
alias='2',
|
||||||
|
operation_type=OperationType.INPUT,
|
||||||
|
result=self.ks2.model
|
||||||
|
)
|
||||||
|
|
||||||
|
self.operation3 = self.owned.create_operation(
|
||||||
|
alias='3',
|
||||||
|
operation_type=OperationType.SYNTHESIS
|
||||||
|
)
|
||||||
|
self.owned.execute_operation(self.operation3)
|
||||||
|
self.operation3.refresh_from_db()
|
||||||
|
self.ks3 = self.operation3.result
|
||||||
|
|
||||||
|
|
||||||
|
@decl_endpoint('/api/library/{item}/set-owner', method='patch')
|
||||||
|
def test_set_owner(self):
|
||||||
|
data = {'user': self.user3.pk}
|
||||||
|
|
||||||
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
self.owned.refresh_from_db()
|
||||||
|
self.ks1.refresh_from_db()
|
||||||
|
self.ks2.refresh_from_db()
|
||||||
|
self.ks3.refresh_from_db()
|
||||||
|
self.assertEqual(self.owned.model.owner, self.user3)
|
||||||
|
self.assertEqual(self.ks1.model.owner, self.user)
|
||||||
|
self.assertEqual(self.ks2.model.owner, self.user2)
|
||||||
|
self.assertEqual(self.ks3.owner, self.user3)
|
||||||
|
|
||||||
|
@decl_endpoint('/api/library/{item}/set-location', method='patch')
|
||||||
|
def test_set_location(self):
|
||||||
|
data = {'location': '/U/temp'}
|
||||||
|
|
||||||
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
self.owned.refresh_from_db()
|
||||||
|
self.ks1.refresh_from_db()
|
||||||
|
self.ks2.refresh_from_db()
|
||||||
|
self.ks3.refresh_from_db()
|
||||||
|
self.assertEqual(self.owned.model.location, data['location'])
|
||||||
|
self.assertNotEqual(self.ks1.model.location, data['location'])
|
||||||
|
self.assertNotEqual(self.ks2.model.location, data['location'])
|
||||||
|
self.assertEqual(self.ks3.location, data['location'])
|
||||||
|
|
||||||
|
@decl_endpoint('/api/library/{item}/set-access-policy', method='patch')
|
||||||
|
def test_set_access_policy(self):
|
||||||
|
data = {'access_policy': AccessPolicy.PROTECTED}
|
||||||
|
|
||||||
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
self.owned.refresh_from_db()
|
||||||
|
self.ks1.refresh_from_db()
|
||||||
|
self.ks2.refresh_from_db()
|
||||||
|
self.ks3.refresh_from_db()
|
||||||
|
self.assertEqual(self.owned.model.access_policy, data['access_policy'])
|
||||||
|
self.assertNotEqual(self.ks1.model.access_policy, data['access_policy'])
|
||||||
|
self.assertNotEqual(self.ks2.model.access_policy, data['access_policy'])
|
||||||
|
self.assertEqual(self.ks3.access_policy, data['access_policy'])
|
|
@ -250,9 +250,8 @@ LOGGING = {
|
||||||
'root': {
|
'root': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO')
|
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO')
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == 'test':
|
if len(sys.argv) > 1 and sys.argv[1] == 'test':
|
||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
''' Utils: base tester class for endpoints. '''
|
''' Utils: base tester class for endpoints. '''
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.db import connection
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APIClient, APIRequestFactory, APITestCase
|
from rest_framework.test import APIClient, APIRequestFactory, APITestCase
|
||||||
|
|
||||||
|
@ -40,6 +43,9 @@ class EndpointTester(APITestCase):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.client.force_authenticate(user=self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
|
self.logger = logging.getLogger('django.db.backends')
|
||||||
|
self.logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
def setUpFullUsers(self):
|
def setUpFullUsers(self):
|
||||||
self.factory = APIRequestFactory()
|
self.factory = APIRequestFactory()
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
|
@ -71,6 +77,16 @@ class EndpointTester(APITestCase):
|
||||||
def logout(self):
|
def logout(self):
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
|
def start_db_log(self):
|
||||||
|
''' Warning! Do not use this second time before calling stop_db_log. '''
|
||||||
|
''' Warning! Do not forget to enable global logging in settings. '''
|
||||||
|
logging.disable(logging.NOTSET)
|
||||||
|
connection.force_debug_cursor = True
|
||||||
|
|
||||||
|
def stop_db_log(self):
|
||||||
|
connection.force_debug_cursor = False
|
||||||
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
def set_params(self, **kwargs):
|
def set_params(self, **kwargs):
|
||||||
''' Given named argument values resolve current endpoint_mask. '''
|
''' Given named argument values resolve current endpoint_mask. '''
|
||||||
if self.endpoint_mask and len(kwargs) > 0:
|
if self.endpoint_mask and len(kwargs) > 0:
|
||||||
|
|
|
@ -17,25 +17,36 @@ interface MiniSelectorOSSProps {
|
||||||
|
|
||||||
function MiniSelectorOSS({ items, onSelect }: MiniSelectorOSSProps) {
|
function MiniSelectorOSS({ items, onSelect }: MiniSelectorOSSProps) {
|
||||||
const ossMenu = useDropdown();
|
const ossMenu = useDropdown();
|
||||||
|
|
||||||
|
function onToggle(event: CProps.EventMouse) {
|
||||||
|
if (items.length > 1) {
|
||||||
|
ossMenu.toggle();
|
||||||
|
} else {
|
||||||
|
onSelect(event, items[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ossMenu.ref} className='flex items-center'>
|
<div ref={ossMenu.ref} className='flex items-center'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<IconOSS size='1.25rem' className='icon-primary' />}
|
icon={<IconOSS size='1.25rem' className='icon-primary' />}
|
||||||
title='Связанные операционные схемы'
|
title='Операционные схемы'
|
||||||
hideTitle={ossMenu.isOpen}
|
hideTitle={ossMenu.isOpen}
|
||||||
onClick={() => ossMenu.toggle()}
|
onClick={onToggle}
|
||||||
/>
|
/>
|
||||||
<Dropdown isOpen={ossMenu.isOpen}>
|
{items.length > 1 ? (
|
||||||
<Label text='Список ОСС' className='border-b px-3 py-1' />
|
<Dropdown isOpen={ossMenu.isOpen}>
|
||||||
{items.map((reference, index) => (
|
<Label text='Список ОСС' className='border-b px-3 py-1' />
|
||||||
<DropdownButton
|
{items.map((reference, index) => (
|
||||||
className='min-w-[5rem]'
|
<DropdownButton
|
||||||
key={`${prefixes.oss_list}${index}`}
|
className='min-w-[5rem]'
|
||||||
text={reference.alias}
|
key={`${prefixes.oss_list}${index}`}
|
||||||
onClick={event => onSelect(event, reference)}
|
text={reference.alias}
|
||||||
/>
|
onClick={event => onSelect(event, reference)}
|
||||||
))}
|
/>
|
||||||
</Dropdown>
|
))}
|
||||||
|
</Dropdown>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ export interface ILibraryItemEditor {
|
||||||
|
|
||||||
isMutable: boolean;
|
isMutable: boolean;
|
||||||
isProcessing: boolean;
|
isProcessing: boolean;
|
||||||
|
isAttachedToOSS: boolean;
|
||||||
|
|
||||||
setOwner: (newOwner: UserID) => void;
|
setOwner: (newOwner: UserID) => void;
|
||||||
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
||||||
|
|
|
@ -15,7 +15,7 @@ import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
||||||
import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
||||||
import DlgEditEditors from '@/dialogs/DlgEditEditors';
|
import DlgEditEditors from '@/dialogs/DlgEditEditors';
|
||||||
import DlgEditOperation from '@/dialogs/DlgEditOperation';
|
import DlgEditOperation from '@/dialogs/DlgEditOperation';
|
||||||
import { AccessPolicy, LibraryItemID } from '@/models/library';
|
import { AccessPolicy, ILibraryItemEditor, LibraryItemID } from '@/models/library';
|
||||||
import { Position2D } from '@/models/miscellaneous';
|
import { Position2D } from '@/models/miscellaneous';
|
||||||
import {
|
import {
|
||||||
IOperationCreateData,
|
IOperationCreateData,
|
||||||
|
@ -37,12 +37,13 @@ export interface ICreateOperationPrompt {
|
||||||
callback: (newID: OperationID) => void;
|
callback: (newID: OperationID) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOssEditContext {
|
export interface IOssEditContext extends ILibraryItemEditor {
|
||||||
schema?: IOperationSchema;
|
schema?: IOperationSchema;
|
||||||
selected: OperationID[];
|
selected: OperationID[];
|
||||||
|
|
||||||
isMutable: boolean;
|
isMutable: boolean;
|
||||||
isProcessing: boolean;
|
isProcessing: boolean;
|
||||||
|
isAttachedToOSS: boolean;
|
||||||
|
|
||||||
showTooltip: boolean;
|
showTooltip: boolean;
|
||||||
setShowTooltip: React.Dispatch<React.SetStateAction<boolean>>;
|
setShowTooltip: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
@ -319,6 +320,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
|
|
||||||
isMutable,
|
isMutable,
|
||||||
isProcessing: model.processing,
|
isProcessing: model.processing,
|
||||||
|
isAttachedToOSS: false,
|
||||||
|
|
||||||
toggleSubscribe,
|
toggleSubscribe,
|
||||||
setOwner,
|
setOwner,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useIntl } from 'react-intl';
|
||||||
import { IconEdit } from '@/components/Icons';
|
import { IconEdit } from '@/components/Icons';
|
||||||
import InfoUsers from '@/components/info/InfoUsers';
|
import InfoUsers from '@/components/info/InfoUsers';
|
||||||
import SelectUser from '@/components/select/SelectUser';
|
import SelectUser from '@/components/select/SelectUser';
|
||||||
|
import LabeledValue from '@/components/ui/LabeledValue';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import Tooltip from '@/components/ui/Tooltip';
|
import Tooltip from '@/components/ui/Tooltip';
|
||||||
|
@ -15,8 +16,6 @@ import { UserID, UserLevel } from '@/models/user';
|
||||||
import { prefixes } from '@/utils/constants';
|
import { prefixes } from '@/utils/constants';
|
||||||
import { prompts } from '@/utils/labels';
|
import { prompts } from '@/utils/labels';
|
||||||
|
|
||||||
import LabeledValue from '@/components/ui/LabeledValue';
|
|
||||||
|
|
||||||
interface EditorLibraryItemProps {
|
interface EditorLibraryItemProps {
|
||||||
item?: ILibraryItemData;
|
item?: ILibraryItemData;
|
||||||
isModified?: boolean;
|
isModified?: boolean;
|
||||||
|
@ -48,11 +47,11 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
{accessLevel >= UserLevel.OWNER ? (
|
{accessLevel >= UserLevel.OWNER ? (
|
||||||
<Overlay position='top-[-0.5rem] left-[2.3rem] cc-icons'>
|
<Overlay position='top-[-0.5rem] left-[2.3rem] cc-icons'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Изменить путь'
|
title={controller.isAttachedToOSS ? 'Путь наследуется от ОСС' : 'Изменить путь'}
|
||||||
noHover
|
noHover
|
||||||
onClick={() => controller.promptLocation()}
|
onClick={() => controller.promptLocation()}
|
||||||
icon={<IconEdit size='1rem' className='mt-1 icon-primary' />}
|
icon={<IconEdit size='1rem' className='mt-1 icon-primary' />}
|
||||||
disabled={isModified || controller.isProcessing}
|
disabled={isModified || controller.isProcessing || controller.isAttachedToOSS}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -66,11 +65,11 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
<Overlay position='top-[-0.5rem] left-[5.5rem] cc-icons'>
|
<Overlay position='top-[-0.5rem] left-[5.5rem] cc-icons'>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Изменить владельца'
|
title={controller.isAttachedToOSS ? 'Владелец наследуется от ОСС' : 'Изменить владельца'}
|
||||||
noHover
|
noHover
|
||||||
onClick={() => ownerSelector.toggle()}
|
onClick={() => ownerSelector.toggle()}
|
||||||
icon={<IconEdit size='1rem' className='mt-1 icon-primary' />}
|
icon={<IconEdit size='1rem' className='mt-1 icon-primary' />}
|
||||||
disabled={isModified || controller.isProcessing}
|
disabled={isModified || controller.isProcessing || controller.isAttachedToOSS}
|
||||||
/>
|
/>
|
||||||
{ownerSelector.isOpen ? (
|
{ownerSelector.isOpen ? (
|
||||||
<SelectUser
|
<SelectUser
|
||||||
|
|
|
@ -33,7 +33,7 @@ function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, c
|
||||||
<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
|
<SelectAccessPolicy
|
||||||
disabled={accessLevel <= UserLevel.EDITOR || controller.isProcessing}
|
disabled={accessLevel <= UserLevel.EDITOR || controller.isProcessing || controller.isAttachedToOSS}
|
||||||
value={policy}
|
value={policy}
|
||||||
onChange={newPolicy => controller.setAccessPolicy(newPolicy)}
|
onChange={newPolicy => controller.setAccessPolicy(newPolicy)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -26,6 +26,7 @@ import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
|
||||||
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
||||||
import {
|
import {
|
||||||
AccessPolicy,
|
AccessPolicy,
|
||||||
|
ILibraryItemEditor,
|
||||||
ILibraryUpdateData,
|
ILibraryUpdateData,
|
||||||
IVersionData,
|
IVersionData,
|
||||||
LibraryItemID,
|
LibraryItemID,
|
||||||
|
@ -55,13 +56,14 @@ import { promptUnsaved } from '@/utils/utils';
|
||||||
|
|
||||||
import { RSTabID } from './RSTabs';
|
import { RSTabID } from './RSTabs';
|
||||||
|
|
||||||
export interface IRSEditContext {
|
export interface IRSEditContext extends ILibraryItemEditor {
|
||||||
schema?: IRSForm;
|
schema?: IRSForm;
|
||||||
selected: ConstituentaID[];
|
selected: ConstituentaID[];
|
||||||
|
|
||||||
isMutable: boolean;
|
isMutable: boolean;
|
||||||
isContentEditable: boolean;
|
isContentEditable: boolean;
|
||||||
isProcessing: boolean;
|
isProcessing: boolean;
|
||||||
|
isAttachedToOSS: boolean;
|
||||||
canProduceStructure: boolean;
|
canProduceStructure: boolean;
|
||||||
nothingSelected: boolean;
|
nothingSelected: boolean;
|
||||||
canDeleteSelected: boolean;
|
canDeleteSelected: boolean;
|
||||||
|
@ -153,6 +155,13 @@ export const RSEditState = ({
|
||||||
() => !nothingSelected && selected.every(id => !model.schema?.cstByID.get(id)?.is_inherited),
|
() => !nothingSelected && selected.every(id => !model.schema?.cstByID.get(id)?.is_inherited),
|
||||||
[selected, nothingSelected, model.schema]
|
[selected, nothingSelected, model.schema]
|
||||||
);
|
);
|
||||||
|
const isAttachedToOSS = useMemo(
|
||||||
|
() =>
|
||||||
|
!!model.schema &&
|
||||||
|
model.schema.oss.length > 0 &&
|
||||||
|
(model.schema.stats.count_inherited > 0 || model.schema.items.length === 0),
|
||||||
|
[model.schema]
|
||||||
|
);
|
||||||
|
|
||||||
const [showUpload, setShowUpload] = useState(false);
|
const [showUpload, setShowUpload] = useState(false);
|
||||||
const [showClone, setShowClone] = useState(false);
|
const [showClone, setShowClone] = useState(false);
|
||||||
|
@ -618,6 +627,7 @@ export const RSEditState = ({
|
||||||
isMutable,
|
isMutable,
|
||||||
isContentEditable,
|
isContentEditable,
|
||||||
isProcessing: model.processing,
|
isProcessing: model.processing,
|
||||||
|
isAttachedToOSS,
|
||||||
canProduceStructure,
|
canProduceStructure,
|
||||||
nothingSelected,
|
nothingSelected,
|
||||||
canDeleteSelected,
|
canDeleteSelected,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user