mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Add library template support for backend
This commit is contained in:
parent
9cd682b19f
commit
72039727ff
|
@ -10,7 +10,8 @@ class ConstituentaAdmin(admin.ModelAdmin):
|
|||
list_display = ['schema', 'alias', 'term_resolved', 'definition_resolved']
|
||||
search_fields = ['term_resolved', 'definition_resolved']
|
||||
|
||||
class LibraryAdmin(admin.ModelAdmin):
|
||||
|
||||
class LibraryItemAdmin(admin.ModelAdmin):
|
||||
''' Admin model: LibraryItem. '''
|
||||
date_hierarchy = 'time_update'
|
||||
list_display = [
|
||||
|
@ -22,6 +23,18 @@ class LibraryAdmin(admin.ModelAdmin):
|
|||
search_fields = ['alias', 'title']
|
||||
|
||||
|
||||
class LibraryTemplateAdmin(admin.ModelAdmin):
|
||||
''' Admin model: LibraryTemplate. '''
|
||||
list_display = ['id', 'alias']
|
||||
list_select_related = ['lib_source']
|
||||
|
||||
def alias(self, template: models.LibraryTemplate):
|
||||
if template.lib_source:
|
||||
return template.lib_source.alias
|
||||
else:
|
||||
return 'N/A'
|
||||
|
||||
|
||||
class SubscriptionAdmin(admin.ModelAdmin):
|
||||
''' Admin model: Subscriptions. '''
|
||||
list_display = ['id', 'item', 'user']
|
||||
|
@ -32,5 +45,6 @@ class SubscriptionAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
admin.site.register(models.Constituenta, ConstituentaAdmin)
|
||||
admin.site.register(models.LibraryItem, LibraryAdmin)
|
||||
admin.site.register(models.LibraryItem, LibraryItemAdmin)
|
||||
admin.site.register(models.LibraryTemplate, LibraryTemplateAdmin)
|
||||
admin.site.register(models.Subscription, SubscriptionAdmin)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 4.2.6 on 2023-10-18 16:12
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rsform', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LibraryTemplate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('lib_source', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rsform.libraryitem', verbose_name='Источник')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Шаблон',
|
||||
'verbose_name_plural': 'Шаблоны',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -71,8 +71,7 @@ def _get_type_prefix(cst_type: CstType) -> str:
|
|||
|
||||
|
||||
class LibraryItem(Model):
|
||||
''' Abstract library item.
|
||||
Please use wrappers below to access functionality. '''
|
||||
''' Abstract library item.'''
|
||||
item_type: CharField = CharField(
|
||||
verbose_name='Тип',
|
||||
max_length=50,
|
||||
|
@ -136,6 +135,21 @@ class LibraryItem(Model):
|
|||
Subscription.subscribe(user=self.owner, item=self)
|
||||
|
||||
|
||||
class LibraryTemplate(Model):
|
||||
''' Template for library items and constituents. '''
|
||||
lib_source: ForeignKey = ForeignKey(
|
||||
verbose_name='Источник',
|
||||
to=LibraryItem,
|
||||
on_delete=CASCADE,
|
||||
null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Шаблон'
|
||||
verbose_name_plural = 'Шаблоны'
|
||||
|
||||
|
||||
class Subscription(Model):
|
||||
''' User subscription to library item. '''
|
||||
user: ForeignKey = ForeignKey(
|
||||
|
@ -319,6 +333,8 @@ class RSForm:
|
|||
''' Insert new constituenta at given position. All following constituents order is shifted by 1 position '''
|
||||
if position <= 0:
|
||||
raise ValidationError('Invalid position: should be positive integer')
|
||||
if self.constituents().filter(alias=alias).exists():
|
||||
raise ValidationError(f'Alias taken {alias}')
|
||||
currentSize = self.constituents().count()
|
||||
position = max(1, min(position, currentSize + 1))
|
||||
update_list = \
|
||||
|
@ -342,6 +358,8 @@ class RSForm:
|
|||
@transaction.atomic
|
||||
def insert_last(self, alias: str, insert_type: CstType) -> 'Constituenta':
|
||||
''' Insert new constituenta at last position '''
|
||||
if self.constituents().filter(alias=alias).exists():
|
||||
raise ValidationError(f'Alias taken {alias}')
|
||||
position = 1
|
||||
if self.constituents().exists():
|
||||
position += self.constituents().count()
|
||||
|
|
|
@ -188,13 +188,18 @@ class CstRenameSerializer(serializers.ModelSerializer):
|
|||
def validate(self, attrs):
|
||||
schema = cast(RSForm, self.context['schema'])
|
||||
old_cst = Constituenta.objects.get(pk=self.initial_data['id'])
|
||||
new_alias = self.initial_data['alias']
|
||||
if old_cst.schema != schema.item:
|
||||
raise serializers.ValidationError({
|
||||
'id': f'Изменяемая конституента должна относиться к изменяемой схеме: {schema.item.title}'
|
||||
})
|
||||
if old_cst.alias == self.initial_data['alias']:
|
||||
if old_cst.alias == new_alias:
|
||||
raise serializers.ValidationError({
|
||||
'alias': f'Имя конституенты должно отличаться от текущего: {self.initial_data["alias"]}'
|
||||
'alias': f'Имя конституенты должно отличаться от текущего: {new_alias}'
|
||||
})
|
||||
if schema.constituents().filter(alias=new_alias).exists():
|
||||
raise serializers.ValidationError({
|
||||
'alias': f'Конституента с таким именем уже существует: {new_alias}'
|
||||
})
|
||||
self.instance = old_cst
|
||||
attrs['schema'] = schema.item
|
||||
|
|
|
@ -190,9 +190,17 @@ class TestRSForm(TestCase):
|
|||
self.assertEqual(cst2.order, 1)
|
||||
self.assertEqual(cst1.order, 2)
|
||||
|
||||
def test_insert_at_invalid_position(self):
|
||||
schema = RSForm.create(title='Test')
|
||||
with self.assertRaises(ValidationError):
|
||||
schema.insert_at(0, 'X5', CstType.BASE)
|
||||
|
||||
def test_insert_at_invalid_alias(self):
|
||||
schema = RSForm.create(title='Test')
|
||||
schema.insert_at(1, 'X1', CstType.BASE)
|
||||
with self.assertRaises(ValidationError):
|
||||
schema.insert_at(2, 'X1', CstType.BASE)
|
||||
|
||||
def test_insert_at_reorder(self):
|
||||
schema = RSForm.create(title='Test')
|
||||
schema.insert_at(1, 'X1', CstType.BASE)
|
||||
|
|
|
@ -8,7 +8,10 @@ from rest_framework.exceptions import ErrorDetail
|
|||
from cctext import ReferenceType, split_grams
|
||||
|
||||
from apps.users.models import User
|
||||
from apps.rsform.models import Syntax, RSForm, Constituenta, CstType, LibraryItem, LibraryItemType, Subscription
|
||||
from apps.rsform.models import (
|
||||
Syntax, RSForm, Constituenta, CstType,
|
||||
LibraryItem, LibraryItemType, Subscription, LibraryTemplate
|
||||
)
|
||||
from apps.rsform.views import (
|
||||
convert_to_ascii,
|
||||
convert_to_math,
|
||||
|
@ -264,6 +267,20 @@ class TestLibraryViewset(APITestCase):
|
|||
self.assertEqual(response.status_code, 204)
|
||||
self.assertFalse(self.user in self.unowned.subscribers())
|
||||
|
||||
def test_retrieve_templates(self):
|
||||
response = self.client.get('/api/library/templates')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(_response_contains(response, self.common))
|
||||
self.assertFalse(_response_contains(response, self.unowned))
|
||||
self.assertFalse(_response_contains(response, self.owned))
|
||||
|
||||
LibraryTemplate.objects.create(lib_source=self.unowned)
|
||||
response = self.client.get('/api/library/templates')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(_response_contains(response, self.common))
|
||||
self.assertTrue(_response_contains(response, self.unowned))
|
||||
self.assertFalse(_response_contains(response, self.owned))
|
||||
|
||||
|
||||
class TestRSFormViewset(APITestCase):
|
||||
''' Testing RSForm view. '''
|
||||
|
@ -420,22 +437,22 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(x4.order, 3)
|
||||
|
||||
def test_rename_constituenta(self):
|
||||
self.cst1 = Constituenta.objects.create(
|
||||
cst1 = Constituenta.objects.create(
|
||||
alias='X1', schema=self.owned.item, order=1, convention='Test',
|
||||
term_raw='Test1', term_resolved='Test1',
|
||||
term_forms=[{'text':'form1', 'tags':'sing,datv'}]
|
||||
)
|
||||
self.cst2 = Constituenta.objects.create(
|
||||
cst2 = Constituenta.objects.create(
|
||||
alias='X2', schema=self.unowned.item, order=1, convention='Test1',
|
||||
term_raw='Test2', term_resolved='Test2'
|
||||
)
|
||||
self.cst3 = Constituenta.objects.create(
|
||||
cst3 = Constituenta.objects.create(
|
||||
alias='X3', schema=self.owned.item, order=2,
|
||||
term_raw='Test3', term_resolved='Test3',
|
||||
definition_raw='Test1', definition_resolved='Test2'
|
||||
)
|
||||
|
||||
data = {'alias': 'D2', 'cst_type': 'term', 'id': self.cst2.pk}
|
||||
data = {'alias': 'D2', 'cst_type': 'term', 'id': cst2.pk}
|
||||
response = self.client.patch(
|
||||
f'/api/rsforms/{self.unowned.item.id}/cst-rename',
|
||||
data=data, format='json'
|
||||
|
@ -448,14 +465,21 @@ class TestRSFormViewset(APITestCase):
|
|||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
data = {'alias': self.cst1.alias, 'cst_type': 'term', 'id': self.cst1.pk}
|
||||
data = {'alias': cst1.alias, 'cst_type': 'term', 'id': cst1.pk}
|
||||
response = self.client.patch(
|
||||
f'/api/rsforms/{self.owned.item.id}/cst-rename',
|
||||
data=data, format='json'
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
data = {'alias': 'D2', 'cst_type': 'term', 'id': self.cst1.pk}
|
||||
data = {'alias': cst3.alias, 'id': cst1.pk}
|
||||
response = self.client.patch(
|
||||
f'/api/rsforms/{self.owned.item.id}/cst-rename',
|
||||
data=data, format='json'
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
data = {'alias': 'D2', 'cst_type': 'term', 'id': cst1.pk}
|
||||
item = self.owned.item
|
||||
d1 = Constituenta.objects.create(schema=item, alias='D1', cst_type='term', order=4)
|
||||
d1.term_raw = '@{X1|plur}'
|
||||
|
@ -463,9 +487,9 @@ class TestRSFormViewset(APITestCase):
|
|||
d1.save()
|
||||
|
||||
self.assertEqual(d1.order, 4)
|
||||
self.assertEqual(self.cst1.order, 1)
|
||||
self.assertEqual(self.cst1.alias, 'X1')
|
||||
self.assertEqual(self.cst1.cst_type, CstType.BASE)
|
||||
self.assertEqual(cst1.order, 1)
|
||||
self.assertEqual(cst1.alias, 'X1')
|
||||
self.assertEqual(cst1.cst_type, CstType.BASE)
|
||||
response = self.client.patch(
|
||||
f'/api/rsforms/{item.id}/cst-rename',
|
||||
data=data, format='json'
|
||||
|
@ -474,13 +498,13 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(response.data['new_cst']['alias'], 'D2')
|
||||
self.assertEqual(response.data['new_cst']['cst_type'], 'term')
|
||||
d1.refresh_from_db()
|
||||
self.cst1.refresh_from_db()
|
||||
cst1.refresh_from_db()
|
||||
self.assertEqual(d1.order, 4)
|
||||
self.assertEqual(d1.term_resolved, '')
|
||||
self.assertEqual(d1.term_raw, '@{D2|plur}')
|
||||
self.assertEqual(self.cst1.order, 1)
|
||||
self.assertEqual(self.cst1.alias, 'D2')
|
||||
self.assertEqual(self.cst1.cst_type, CstType.TERM)
|
||||
self.assertEqual(cst1.order, 1)
|
||||
self.assertEqual(cst1.alias, 'D2')
|
||||
self.assertEqual(cst1.cst_type, CstType.TERM)
|
||||
|
||||
def test_create_constituenta_data(self):
|
||||
data = {
|
||||
|
|
|
@ -9,6 +9,7 @@ library_router.register('rsforms', views.RSFormViewSet)
|
|||
|
||||
urlpatterns = [
|
||||
path('library/active', views.LibraryActiveView.as_view(), name='library'),
|
||||
path('library/templates', views.LibraryTemplatesView.as_view(), name='library'),
|
||||
path('constituents/<int:pk>', views.ConstituentAPIView.as_view(), name='constituenta-detail'),
|
||||
path('rsforms/import-trs', views.TrsImportView.as_view()),
|
||||
path('rsforms/create-detailed', views.create_rsform),
|
||||
|
|
|
@ -22,7 +22,7 @@ from . import utils
|
|||
@extend_schema(tags=['Library'])
|
||||
@extend_schema_view()
|
||||
class LibraryActiveView(generics.ListAPIView):
|
||||
''' Endpoint: Get list of rsforms available for active user. '''
|
||||
''' Endpoint: Get list of library items available for active user. '''
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = s.LibraryItemSerializer
|
||||
|
||||
|
@ -35,6 +35,18 @@ class LibraryActiveView(generics.ListAPIView):
|
|||
).distinct().order_by('-time_update')
|
||||
else:
|
||||
return m.LibraryItem.objects.filter(is_common=True).order_by('-time_update')
|
||||
|
||||
|
||||
@extend_schema(tags=['Library'])
|
||||
@extend_schema_view()
|
||||
class LibraryTemplatesView(generics.ListAPIView):
|
||||
''' Endpoint: Get list of templates. '''
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
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)
|
||||
|
||||
|
||||
@extend_schema(tags=['Constituenta'])
|
||||
|
|
Loading…
Reference in New Issue
Block a user