mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
R: Cleanup model API: move logic to view/serializer
This commit is contained in:
parent
ec358911fb
commit
ad1d51e8d8
|
@ -1,7 +1,6 @@
|
|||
''' Models: LibraryItem. '''
|
||||
import re
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import (
|
||||
SET_NULL,
|
||||
BooleanField,
|
||||
|
@ -16,7 +15,6 @@ from django.db.models import (
|
|||
|
||||
from apps.users.models import User
|
||||
|
||||
from .Subscription import Subscription
|
||||
from .Version import Version
|
||||
|
||||
|
||||
|
@ -125,34 +123,3 @@ class LibraryItem(Model):
|
|||
def versions(self) -> QuerySet[Version]:
|
||||
''' Get all Versions of this item. '''
|
||||
return Version.objects.filter(item=self.pk).order_by('-time_create')
|
||||
|
||||
# TODO: move to View layer
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
''' Save updating subscriptions and connected operations. '''
|
||||
if not self._state.adding:
|
||||
self._update_connected_operations()
|
||||
subscribe = self._state.adding and self.owner
|
||||
super().save(*args, **kwargs)
|
||||
if subscribe:
|
||||
Subscription.subscribe(user=self.owner_id, item=self.pk)
|
||||
|
||||
def _update_connected_operations(self):
|
||||
# using method level import to prevent circular dependency
|
||||
from apps.oss.models import Operation # pylint: disable=import-outside-toplevel
|
||||
operations = Operation.objects.filter(result__pk=self.pk)
|
||||
if not operations.exists():
|
||||
return
|
||||
for operation in operations:
|
||||
changed = False
|
||||
if operation.alias != self.alias:
|
||||
operation.alias = self.alias
|
||||
changed = True
|
||||
if operation.title != self.title:
|
||||
operation.title = self.title
|
||||
changed = True
|
||||
if operation.comment != self.comment:
|
||||
operation.comment = self.comment
|
||||
changed = True
|
||||
if changed:
|
||||
operation.save()
|
||||
|
|
|
@ -68,7 +68,6 @@ class TestLibraryItem(TestCase):
|
|||
self.assertEqual(item.alias, 'KS1')
|
||||
self.assertEqual(item.comment, 'Test comment')
|
||||
self.assertEqual(item.location, LocationHead.COMMON)
|
||||
self.assertTrue(Subscription.objects.filter(user=item.owner, item=item).exists())
|
||||
|
||||
|
||||
class TestLocation(TestCase):
|
||||
|
|
|
@ -21,9 +21,7 @@ class TestSubscription(TestCase):
|
|||
|
||||
def test_default(self):
|
||||
subs = list(Subscription.objects.filter(item=self.item))
|
||||
self.assertEqual(len(subs), 1)
|
||||
self.assertEqual(subs[0].item, self.item)
|
||||
self.assertEqual(subs[0].user, self.user1)
|
||||
self.assertEqual(len(subs), 0)
|
||||
|
||||
|
||||
def test_str(self):
|
||||
|
|
|
@ -49,6 +49,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
self.assertEqual(response.data['item_type'], LibraryItemType.RSFORM)
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
self.assertEqual(response.data['alias'], data['alias'])
|
||||
self.assertTrue(Subscription.objects.filter(user=self.user, item_id=response.data['id']).exists())
|
||||
|
||||
data = {
|
||||
'item_type': LibraryItemType.OPERATION_SCHEMA,
|
||||
|
@ -74,7 +75,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
|
||||
@decl_endpoint('/api/library/{item}', method='patch')
|
||||
def test_update(self):
|
||||
data = {'id': self.unowned.pk, 'title': 'New Title'}
|
||||
data = {'title': 'New Title'}
|
||||
self.executeNotFound(data=data, item=self.invalid_item)
|
||||
self.executeForbidden(data=data, item=self.unowned.pk)
|
||||
|
||||
|
@ -86,13 +87,12 @@ class TestLibraryViewset(EndpointTester):
|
|||
self.unowned.save()
|
||||
self.executeForbidden(data=data, item=self.unowned.pk)
|
||||
|
||||
data = {'id': self.owned.pk, 'title': 'New Title'}
|
||||
data = {'title': 'New Title'}
|
||||
response = self.executeOK(data=data, item=self.owned.pk)
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
self.assertEqual(response.data['alias'], self.owned.alias)
|
||||
|
||||
data = {
|
||||
'id': self.owned.pk,
|
||||
'title': 'Another Title',
|
||||
'owner': self.user2.pk,
|
||||
'access_policy': AccessPolicy.PROTECTED,
|
||||
|
|
|
@ -13,7 +13,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
from apps.oss.models import OperationSchema
|
||||
from apps.oss.models import Operation, OperationSchema
|
||||
from apps.rsform.models import RSForm
|
||||
from apps.rsform.serializers import RSFormParseSerializer
|
||||
from apps.users.models import User
|
||||
|
@ -37,11 +37,35 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
return s.LibraryItemBaseSerializer
|
||||
return s.LibraryItemSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
def perform_create(self, serializer) -> None:
|
||||
if not self.request.user.is_anonymous and 'owner' not in self.request.POST:
|
||||
return serializer.save(owner=self.request.user)
|
||||
instance = serializer.save(owner=self.request.user)
|
||||
else:
|
||||
return serializer.save()
|
||||
instance = serializer.save()
|
||||
if instance.owner:
|
||||
m.Subscription.subscribe(user=instance.owner_id, item=instance.pk)
|
||||
|
||||
def perform_update(self, serializer) -> None:
|
||||
instance = serializer.save()
|
||||
operations = Operation.objects.filter(result__pk=instance.pk)
|
||||
if not operations.exists():
|
||||
return
|
||||
update_list: list[Operation] = []
|
||||
for operation in operations:
|
||||
changed = False
|
||||
if operation.alias != instance.alias:
|
||||
operation.alias = instance.alias
|
||||
changed = True
|
||||
if operation.title != instance.title:
|
||||
operation.title = instance.title
|
||||
changed = True
|
||||
if operation.comment != instance.comment:
|
||||
operation.comment = instance.comment
|
||||
changed = True
|
||||
if changed:
|
||||
update_list.append(operation)
|
||||
if update_list:
|
||||
Operation.objects.bulk_update(update_list, ['alias', 'title', 'comment'])
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action in ['update', 'partial_update']:
|
||||
|
|
|
@ -4,7 +4,7 @@ from typing import Optional
|
|||
from django.db.models import QuerySet
|
||||
|
||||
from apps.library.models import Editor, LibraryItem, LibraryItemType
|
||||
from apps.rsform.models import RSForm
|
||||
from apps.rsform.models import Constituenta, RSForm
|
||||
|
||||
from .Argument import Argument
|
||||
from .Inheritance import Inheritance
|
||||
|
@ -186,10 +186,12 @@ class OperationSchema:
|
|||
parents[cst.pk] = items[i]
|
||||
children[items[i].pk] = cst
|
||||
|
||||
translated_substitutions: list[tuple[Constituenta, Constituenta]] = []
|
||||
for sub in substitutions:
|
||||
original = children[sub.original.pk]
|
||||
replacement = children[sub.substitution.pk]
|
||||
receiver.substitute(original, replacement)
|
||||
translated_substitutions.append((original, replacement))
|
||||
receiver.substitute(translated_substitutions)
|
||||
|
||||
# TODO: remove duplicates from diamond
|
||||
|
||||
|
|
|
@ -31,36 +31,3 @@ class TestOperation(TestCase):
|
|||
self.assertEqual(self.operation.comment, '')
|
||||
self.assertEqual(self.operation.position_x, 0)
|
||||
self.assertEqual(self.operation.position_y, 0)
|
||||
|
||||
|
||||
def test_sync_from_result(self):
|
||||
schema = RSForm.create(alias=self.operation.alias)
|
||||
self.operation.result = schema.model
|
||||
self.operation.save()
|
||||
|
||||
schema.model.alias = 'KS2'
|
||||
schema.model.comment = 'Comment'
|
||||
schema.model.title = 'Title'
|
||||
schema.save()
|
||||
self.operation.refresh_from_db()
|
||||
|
||||
self.assertEqual(self.operation.result, schema.model)
|
||||
self.assertEqual(self.operation.alias, schema.model.alias)
|
||||
self.assertEqual(self.operation.title, schema.model.title)
|
||||
self.assertEqual(self.operation.comment, schema.model.comment)
|
||||
|
||||
def test_sync_from_library_item(self):
|
||||
schema = LibraryItem.objects.create(alias=self.operation.alias, item_type=LibraryItemType.RSFORM)
|
||||
self.operation.result = schema
|
||||
self.operation.save()
|
||||
|
||||
schema.alias = 'KS2'
|
||||
schema.comment = 'Comment'
|
||||
schema.title = 'Title'
|
||||
schema.save()
|
||||
self.operation.refresh_from_db()
|
||||
|
||||
self.assertEqual(self.operation.result, schema)
|
||||
self.assertEqual(self.operation.alias, schema.alias)
|
||||
self.assertEqual(self.operation.title, schema.title)
|
||||
self.assertEqual(self.operation.comment, schema.comment)
|
||||
|
|
|
@ -123,3 +123,33 @@ class TestChangeAttributes(EndpointTester):
|
|||
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]))
|
||||
|
||||
@decl_endpoint('/api/library/{item}', method='patch')
|
||||
def test_sync_from_result(self):
|
||||
data = {'alias': 'KS111', 'title': 'New Title', 'comment': 'New Comment'}
|
||||
|
||||
self.executeOK(data=data, item=self.ks1.model.pk)
|
||||
self.operation1.refresh_from_db()
|
||||
|
||||
self.assertEqual(self.operation1.result, self.ks1.model)
|
||||
self.assertEqual(self.operation1.alias, data['alias'])
|
||||
self.assertEqual(self.operation1.title, data['title'])
|
||||
self.assertEqual(self.operation1.comment, data['comment'])
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||
def test_sync_from_operation(self):
|
||||
data = {
|
||||
'target': self.operation3.pk,
|
||||
'item_data': {
|
||||
'alias': 'Test3 mod',
|
||||
'title': 'Test title mod',
|
||||
'comment': 'Comment mod'
|
||||
},
|
||||
'positions': [],
|
||||
}
|
||||
|
||||
response = self.executeOK(data=data, item=self.owned_id)
|
||||
self.ks3.refresh_from_db()
|
||||
self.assertEqual(self.ks3.alias, data['item_data']['alias'])
|
||||
self.assertEqual(self.ks3.title, data['item_data']['title'])
|
||||
self.assertEqual(self.ks3.comment, data['item_data']['comment'])
|
||||
|
|
|
@ -79,14 +79,14 @@ class RSForm:
|
|||
|
||||
def remove(self, target: Constituenta) -> None:
|
||||
if self.is_loaded:
|
||||
self.constituents.remove(target)
|
||||
self.constituents.remove(self.by_id[target.pk])
|
||||
del self.by_id[target.pk]
|
||||
del self.by_alias[target.alias]
|
||||
|
||||
def remove_multi(self, target: Iterable[Constituenta]) -> None:
|
||||
if self.is_loaded:
|
||||
for cst in target:
|
||||
self.constituents.remove(cst)
|
||||
self.constituents.remove(self.by_id[cst.pk])
|
||||
del self.by_id[cst.pk]
|
||||
del self.by_alias[cst.alias]
|
||||
|
||||
|
@ -306,18 +306,20 @@ class RSForm:
|
|||
self.resolve_all_text()
|
||||
self.save()
|
||||
|
||||
def substitute(
|
||||
self,
|
||||
original: Constituenta,
|
||||
substitution: Constituenta
|
||||
) -> None:
|
||||
def substitute(self, substitutions: list[tuple[Constituenta, Constituenta]]) -> None:
|
||||
''' Execute constituenta substitution. '''
|
||||
assert original.pk != substitution.pk
|
||||
mapping = {original.alias: substitution.alias}
|
||||
mapping = {}
|
||||
deleted: list[Constituenta] = []
|
||||
replacements: list[Constituenta] = []
|
||||
for original, substitution in substitutions:
|
||||
assert original.pk != substitution.pk
|
||||
mapping[original.alias] = substitution.alias
|
||||
deleted.append(original)
|
||||
replacements.append(substitution)
|
||||
self.cache.remove_multi(deleted)
|
||||
Constituenta.objects.filter(pk__in=[cst.pk for cst in deleted]).delete()
|
||||
self.apply_mapping(mapping)
|
||||
self.cache.remove(self.cache.by_id[original.pk])
|
||||
original.delete()
|
||||
self.on_term_change([substitution.pk])
|
||||
self.on_term_change([substitution.pk for substitution in replacements])
|
||||
|
||||
def restore_order(self) -> None:
|
||||
''' Restore order based on types and term graph. '''
|
||||
|
|
|
@ -207,7 +207,7 @@ class TestRSForm(DBTester):
|
|||
definition_formal=x1.alias
|
||||
)
|
||||
|
||||
self.schema.substitute(x1, x2)
|
||||
self.schema.substitute([(x1, x2)])
|
||||
x2.refresh_from_db()
|
||||
d1.refresh_from_db()
|
||||
self.assertEqual(self.schema.constituents().count(), 2)
|
||||
|
|
|
@ -100,7 +100,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
self.assertEqual(response.data['items'][1]['id'], x2.pk)
|
||||
self.assertEqual(response.data['items'][1]['term_raw'], x2.term_raw)
|
||||
self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved)
|
||||
self.assertEqual(response.data['subscribers'], [self.user.pk])
|
||||
self.assertEqual(response.data['subscribers'], [])
|
||||
self.assertEqual(response.data['editors'], [])
|
||||
self.assertEqual(response.data['inheritance'], [])
|
||||
self.assertEqual(response.data['oss'], [])
|
||||
|
|
|
@ -226,10 +226,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
with transaction.atomic():
|
||||
substitutions: list[tuple[m.Constituenta, m.Constituenta]] = []
|
||||
for substitution in serializer.validated_data['substitutions']:
|
||||
original = cast(m.Constituenta, substitution['original'])
|
||||
replacement = cast(m.Constituenta, substitution['substitution'])
|
||||
m.RSForm(schema).substitute(original, replacement)
|
||||
substitutions.append((original, replacement))
|
||||
m.RSForm(schema).substitute(substitutions)
|
||||
|
||||
schema.refresh_from_db()
|
||||
return Response(
|
||||
|
@ -574,6 +576,7 @@ def inline_synthesis(request: Request) -> HttpResponse:
|
|||
|
||||
with transaction.atomic():
|
||||
new_items = receiver.insert_copy(items)
|
||||
substitutions: list[tuple[m.Constituenta, m.Constituenta]] = []
|
||||
for substitution in serializer.validated_data['substitutions']:
|
||||
original = cast(m.Constituenta, substitution['original'])
|
||||
replacement = cast(m.Constituenta, substitution['substitution'])
|
||||
|
@ -583,7 +586,8 @@ def inline_synthesis(request: Request) -> HttpResponse:
|
|||
else:
|
||||
index = next(i for (i, cst) in enumerate(items) if cst.pk == replacement.pk)
|
||||
replacement = new_items[index]
|
||||
receiver.substitute(original, replacement)
|
||||
substitutions.append((original, replacement))
|
||||
receiver.substitute(substitutions)
|
||||
receiver.restore_order()
|
||||
|
||||
return Response(
|
||||
|
|
|
@ -134,7 +134,6 @@ class TestUserUserProfileAPIView(EndpointTester):
|
|||
def test_password_reset_request(self):
|
||||
self.executeBadData({'email': 'invalid@mail.ru'})
|
||||
self.executeOK({'email': self.user.email})
|
||||
# TODO: check if mail server actually sent email and if reset procedure works
|
||||
|
||||
|
||||
class TestSignupAPIView(EndpointTester):
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { FolderTree } from './FolderTree';
|
||||
|
||||
// TODO: test FolderNode and FolderTree exhaustively
|
||||
|
||||
describe('Testing Tree construction', () => {
|
||||
test('empty Tree should be empty', () => {
|
||||
const tree = new FolderTree();
|
||||
|
|
Loading…
Reference in New Issue
Block a user