F: Implement editors change for OSS -> RSForm

This commit is contained in:
Ivan 2024-08-06 22:35:53 +03:00
parent 50760d7d13
commit be76908788
13 changed files with 177 additions and 56 deletions

View File

@ -0,0 +1,21 @@
# Generated by Django 5.0.7 on 2024-08-06 19:31
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='editor',
name='editor',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Редактор'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 5.0.7 on 2024-08-06 19:35
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0002_alter_editor_editor'),
]
operations = [
migrations.AlterField(
model_name='librarytemplate',
name='lib_source',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library.libraryitem', verbose_name='Источник'),
),
]

View File

@ -1,14 +1,11 @@
''' Models: Editor. ''' ''' Models: Editor. '''
from typing import TYPE_CHECKING from typing import Iterable
from django.db import transaction from django.db import transaction
from django.db.models import CASCADE, DateTimeField, ForeignKey, Model from django.db.models import CASCADE, DateTimeField, ForeignKey, Model
from apps.users.models import User from apps.users.models import User
if TYPE_CHECKING:
from .LibraryItem import LibraryItem
class Editor(Model): class Editor(Model):
''' Editor list. ''' ''' Editor list. '''
@ -20,8 +17,7 @@ class Editor(Model):
editor: ForeignKey = ForeignKey( editor: ForeignKey = ForeignKey(
verbose_name='Редактор', verbose_name='Редактор',
to=User, to=User,
on_delete=CASCADE, on_delete=CASCADE
null=True
) )
time_create: DateTimeField = DateTimeField( time_create: DateTimeField = DateTimeField(
verbose_name='Дата добавления', verbose_name='Дата добавления',
@ -38,17 +34,17 @@ class Editor(Model):
return f'{self.item}: {self.editor}' return f'{self.item}: {self.editor}'
@staticmethod @staticmethod
def add(item: 'LibraryItem', user: User) -> bool: def add(item: int, user: int) -> bool:
''' Add Editor for item. ''' ''' Add Editor for item. '''
if Editor.objects.filter(item=item, editor=user).exists(): if Editor.objects.filter(item_id=item, editor_id=user).exists():
return False return False
Editor.objects.create(item=item, editor=user) Editor.objects.create(item_id=item, editor_id=user)
return True return True
@staticmethod @staticmethod
def remove(item: 'LibraryItem', user: User) -> bool: def remove(item: int, user: int) -> bool:
''' Remove Editor. ''' ''' Remove Editor. '''
editor = Editor.objects.filter(item=item, editor=user) editor = Editor.objects.filter(item_id=item, editor_id=user).only('pk')
if not editor.exists(): if not editor.exists():
return False return False
editor.delete() editor.delete()
@ -56,16 +52,40 @@ class Editor(Model):
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def set(item: 'LibraryItem', users: list[User]): def set(item: int, users: Iterable[int]):
''' Set editors for item. ''' ''' Set editors for item. '''
processed: list[User] = [] processed: set[int] = set()
for editor_item in Editor.objects.filter(item=item): for editor_item in Editor.objects.filter(item_id=item).only('pk', 'editor_id'):
if editor_item.editor not in users: editor_id = editor_item.editor_id
if editor_id not in users:
editor_item.delete() editor_item.delete()
else: else:
processed.append(editor_item.editor) processed.add(editor_id)
for user in users:
if user not in processed:
processed.add(user)
Editor.objects.create(item_id=item, editor_id=user)
@staticmethod
@transaction.atomic
def set_and_return_diff(item: int, users: Iterable[int]) -> tuple[list[int], list[int]]:
''' Set editors for item and return diff. '''
processed: list[int] = []
deleted: list[int] = []
added: list[int] = []
for editor_item in Editor.objects.filter(item_id=item).only('pk', 'editor_id'):
editor_id = editor_item.editor_id
if editor_id not in users:
deleted.append(editor_id)
editor_item.delete()
else:
processed.append(editor_id)
for user in users: for user in users:
if user not in processed: if user not in processed:
processed.append(user) processed.append(user)
Editor.objects.create(item=item, editor=user) added.append(user)
Editor.objects.create(item_id=item, editor_id=user)
return (added, deleted)

View File

@ -16,7 +16,6 @@ from django.db.models import (
from apps.users.models import User from apps.users.models import User
from .Editor import Editor
from .Subscription import Subscription from .Subscription import Subscription
from .Version import Version from .Version import Version
@ -119,9 +118,9 @@ class LibraryItem(Model):
''' Get all subscribers for this item. ''' ''' Get all subscribers for this item. '''
return [subscription.user for subscription in Subscription.objects.filter(item=self.pk).only('user')] return [subscription.user for subscription in Subscription.objects.filter(item=self.pk).only('user')]
def editors(self) -> list[User]: def editors(self) -> QuerySet[User]:
''' Get all Editors of this item. ''' ''' Get all Editors of this item. '''
return [item.editor for item in Editor.objects.filter(item=self.pk).only('editor')] return User.objects.filter(editor__item=self.pk)
def versions(self) -> QuerySet[Version]: def versions(self) -> QuerySet[Version]:
''' Get all Versions of this item. ''' ''' Get all Versions of this item. '''

View File

@ -7,8 +7,7 @@ class LibraryTemplate(Model):
lib_source: ForeignKey = ForeignKey( lib_source: ForeignKey = ForeignKey(
verbose_name='Источник', verbose_name='Источник',
to='library.LibraryItem', to='library.LibraryItem',
on_delete=CASCADE, on_delete=CASCADE
null=True
) )
class Meta: class Meta:

View File

@ -86,7 +86,7 @@ class LibraryItemDetailsSerializer(serializers.ModelSerializer):
return [item.pk for item in instance.subscribers()] return [item.pk for item in instance.subscribers()]
def get_editors(self, instance: LibraryItem) -> list[int]: def get_editors(self, instance: LibraryItem) -> list[int]:
return [item.pk for item in instance.editors()] return list(instance.editors().values_list('pk', flat=True))
def get_versions(self, instance: LibraryItem) -> list: def get_versions(self, instance: LibraryItem) -> list:
return [VersionInnerSerializer(item).data for item in instance.versions()] return [VersionInnerSerializer(item).data for item in instance.versions()]

View File

@ -34,44 +34,65 @@ class TestEditor(TestCase):
def test_add_editor(self): def test_add_editor(self):
self.assertTrue(Editor.add(self.item, self.user1)) self.assertTrue(Editor.add(self.item.pk, self.user1.pk))
self.assertEqual(len(self.item.editors()), 1) self.assertEqual(self.item.editors().count(), 1)
self.assertTrue(self.user1 in self.item.editors()) self.assertTrue(self.user1 in list(self.item.editors()))
self.assertFalse(Editor.add(self.item, self.user1)) self.assertFalse(Editor.add(self.item.pk, self.user1.pk))
self.assertEqual(len(self.item.editors()), 1) self.assertEqual(self.item.editors().count(), 1)
self.assertTrue(Editor.add(self.item, self.user2)) self.assertTrue(Editor.add(self.item.pk, self.user2.pk))
self.assertEqual(len(self.item.editors()), 2) self.assertEqual(self.item.editors().count(), 2)
self.assertTrue(self.user1 in self.item.editors()) self.assertTrue(self.user1 in self.item.editors())
self.assertTrue(self.user2 in self.item.editors()) self.assertTrue(self.user2 in self.item.editors())
self.user1.delete() self.user1.delete()
self.assertEqual(len(self.item.editors()), 1) self.assertEqual(self.item.editors().count(), 1)
def test_remove_editor(self): def test_remove_editor(self):
self.assertFalse(Editor.remove(self.item, self.user1)) self.assertFalse(Editor.remove(self.item.pk, self.user1.pk))
Editor.add(self.item, self.user1) Editor.add(self.item.pk, self.user1.pk)
Editor.add(self.item, self.user2) Editor.add(self.item.pk, self.user2.pk)
self.assertEqual(len(self.item.editors()), 2) self.assertEqual(self.item.editors().count(), 2)
self.assertTrue(Editor.remove(self.item, self.user1)) self.assertTrue(Editor.remove(self.item.pk, self.user1.pk))
self.assertEqual(len(self.item.editors()), 1) self.assertEqual(self.item.editors().count(), 1)
self.assertTrue(self.user2 in self.item.editors()) self.assertTrue(self.user2 in self.item.editors())
self.assertFalse(Editor.remove(self.item, self.user1)) self.assertFalse(Editor.remove(self.item.pk, self.user1.pk))
def test_set_editors(self): def test_set_editors(self):
Editor.set(self.item, [self.user1]) Editor.set(self.item.pk, [self.user1.pk])
self.assertEqual(self.item.editors(), [self.user1]) self.assertEqual(list(self.item.editors()), [self.user1])
Editor.set(self.item, [self.user1, self.user1]) Editor.set(self.item.pk, [self.user1.pk, self.user1.pk])
self.assertEqual(self.item.editors(), [self.user1]) self.assertEqual(list(self.item.editors()), [self.user1])
Editor.set(self.item, []) Editor.set(self.item.pk, [])
self.assertEqual(self.item.editors(), []) self.assertEqual(list(self.item.editors()), [])
Editor.set(self.item, [self.user1, self.user2]) Editor.set(self.item.pk, [self.user1.pk, self.user2.pk])
self.assertEqual(set(self.item.editors()), set([self.user1, self.user2]))
def test_set_editors_return_diff(self):
added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk])
self.assertEqual(added, [self.user1.pk])
self.assertEqual(deleted, [])
self.assertEqual(list(self.item.editors()), [self.user1])
added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk, self.user1.pk])
self.assertEqual(added, [])
self.assertEqual(deleted, [])
self.assertEqual(list(self.item.editors()), [self.user1])
added, deleted = Editor.set_and_return_diff(self.item.pk, [])
self.assertEqual(added, [])
self.assertEqual(deleted, [self.user1.pk])
self.assertEqual(list(self.item.editors()), [])
added, deleted = Editor.set_and_return_diff(self.item.pk, [self.user1.pk, self.user2.pk])
self.assertEqual(added, [self.user1.pk, self.user2.pk])
self.assertEqual(deleted, [])
self.assertEqual(set(self.item.editors()), set([self.user1, self.user2])) self.assertEqual(set(self.item.editors()), set([self.user1, self.user2]))

View File

@ -197,18 +197,18 @@ class TestLibraryViewset(EndpointTester):
self.executeOK(data=data, item=self.owned.pk) self.executeOK(data=data, item=self.owned.pk)
self.owned.refresh_from_db() self.owned.refresh_from_db()
self.assertEqual(self.owned.time_update, time_update) self.assertEqual(self.owned.time_update, time_update)
self.assertEqual(self.owned.editors(), [self.user]) self.assertEqual(list(self.owned.editors()), [self.user])
self.executeOK(data=data) self.executeOK(data=data)
self.assertEqual(self.owned.editors(), [self.user]) self.assertEqual(list(self.owned.editors()), [self.user])
data = {'users': [self.user2.pk]} data = {'users': [self.user2.pk]}
self.executeOK(data=data) self.executeOK(data=data)
self.assertEqual(self.owned.editors(), [self.user2]) self.assertEqual(list(self.owned.editors()), [self.user2])
data = {'users': []} data = {'users': []}
self.executeOK(data=data) self.executeOK(data=data)
self.assertEqual(self.owned.editors(), []) self.assertEqual(list(self.owned.editors()), [])
data = {'users': [self.user2.pk, self.user.pk]} data = {'users': [self.user2.pk, self.user.pk]}
self.executeOK(data=data) self.executeOK(data=data)

View File

@ -261,8 +261,32 @@ class LibraryViewSet(viewsets.ModelViewSet):
item = self._get_item() item = self._get_item()
serializer = s.UsersListSerializer(data=request.data) serializer = s.UsersListSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
editors = serializer.validated_data['users'] editors: list[int] = request.data['users']
m.Editor.set(item=item, users=editors)
with transaction.atomic():
added, deleted = m.Editor.set_and_return_diff(item.pk, editors)
if len(added) >= 0 or len(deleted) >= 0:
owned_schemas = OperationSchema(item).owned_schemas().only('pk')
if owned_schemas.exists():
m.Editor.objects.filter(
item__in=owned_schemas,
editor_id__in=deleted
).delete()
existing_editors = m.Editor.objects.filter(
item__in=owned_schemas,
editor__in=added
).values_list('item_id', 'editor_id')
existing_editor_set = set(existing_editors)
new_editors = [
m.Editor(item=schema, editor_id=user)
for schema in owned_schemas
for user in added
if (item.id, user) not in existing_editor_set
]
m.Editor.objects.bulk_create(new_editors)
return Response(status=c.HTTP_200_OK) return Response(status=c.HTTP_200_OK)

View File

@ -169,7 +169,7 @@ class OperationSchema:
access_policy=self.model.access_policy, access_policy=self.model.access_policy,
location=self.model.location location=self.model.location
) )
Editor.set(schema.model, self.model.editors()) Editor.set(schema.model.pk, self.model.editors().values_list('pk', flat=True))
operation.result = schema.model operation.result = schema.model
operation.save() operation.save()
self.save() self.save()

View File

@ -2,7 +2,7 @@
from rest_framework import status from rest_framework import status
from apps.library.models import AccessPolicy, LocationHead from apps.library.models import AccessPolicy, Editor, LocationHead
from apps.oss.models import Operation, OperationSchema, OperationType from apps.oss.models import Operation, OperationSchema, OperationType
from apps.rsform.models import RSForm from apps.rsform.models import RSForm
from apps.users.models import User from apps.users.models import User
@ -105,3 +105,21 @@ class TestChangeAttributes(EndpointTester):
self.assertNotEqual(self.ks1.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.assertNotEqual(self.ks2.model.access_policy, data['access_policy'])
self.assertEqual(self.ks3.access_policy, data['access_policy']) self.assertEqual(self.ks3.access_policy, data['access_policy'])
@decl_endpoint('/api/library/{item}/set-editors', method='patch')
def test_set_editors(self):
Editor.set(self.owned.model.pk, [self.user2.pk])
Editor.set(self.ks1.model.pk, [self.user2.pk, self.user.pk])
Editor.set(self.ks3.pk, [self.user2.pk, self.user.pk])
data = {'users': [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(list(self.owned.model.editors()), [self.user3])
self.assertEqual(list(self.ks1.model.editors()), [self.user, self.user2])
self.assertEqual(list(self.ks2.model.editors()), [])
self.assertEqual(set(self.ks3.editors()), set([self.user, self.user3]))

View File

@ -220,7 +220,7 @@ class TestOssViewset(EndpointTester):
@decl_endpoint('/api/oss/{item}/create-operation', method='post') @decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_schema(self): def test_create_operation_schema(self):
self.populateData() self.populateData()
Editor.add(self.owned.model, self.user2) Editor.add(self.owned.model.pk, self.user2.pk)
data = { data = {
'item_data': { 'item_data': {
'alias': 'Test4', 'alias': 'Test4',

View File

@ -67,9 +67,9 @@ class EndpointTester(APITestCase):
def toggle_editor(self, item: LibraryItem, value: bool = True): def toggle_editor(self, item: LibraryItem, value: bool = True):
if value: if value:
Editor.add(item, self.user) Editor.add(item.pk, self.user.pk)
else: else:
Editor.remove(item, self.user) Editor.remove(item.pk, self.user.pk)
def login(self): def login(self):
self.client.force_authenticate(user=self.user) self.client.force_authenticate(user=self.user)