mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 21:10:38 +03:00
Refactor backend API generation and admin UI
This commit is contained in:
parent
f21a01bbc0
commit
b6f14fdbe1
|
@ -6,14 +6,29 @@ from . import models
|
|||
|
||||
class ConstituentaAdmin(admin.ModelAdmin):
|
||||
''' Admin model: Constituenta. '''
|
||||
|
||||
ordering = ['schema', 'order']
|
||||
list_display = ['schema', 'alias', 'term_resolved', 'definition_resolved']
|
||||
search_fields = ['term_resolved', 'definition_resolved']
|
||||
|
||||
class LibraryAdmin(admin.ModelAdmin):
|
||||
''' Admin model: LibraryItem. '''
|
||||
date_hierarchy = 'time_update'
|
||||
list_display = [
|
||||
'alias', 'title', 'owner',
|
||||
'is_common', 'is_canonical',
|
||||
'time_update'
|
||||
]
|
||||
list_filter = ['is_common', 'is_canonical', 'time_update']
|
||||
search_fields = ['alias', 'title']
|
||||
|
||||
|
||||
class SubscriptionAdmin(admin.ModelAdmin):
|
||||
''' Admin model: Subscriptions. '''
|
||||
list_display = ['id', 'item', 'user']
|
||||
search_fields = [
|
||||
'item__title', 'item__alias',
|
||||
'user__username', 'user__first_name', 'user__last_name'
|
||||
]
|
||||
|
||||
|
||||
admin.site.register(models.Constituenta, ConstituentaAdmin)
|
||||
|
|
|
@ -122,7 +122,7 @@ class LibraryItem(Model):
|
|||
return f'{self.title}'
|
||||
|
||||
def get_absolute_url(self):
|
||||
return f'/api/library/{self.pk}/'
|
||||
return f'/api/library/{self.pk}'
|
||||
|
||||
def subscribers(self) -> list[User]:
|
||||
''' Get all subscribers for this item . '''
|
||||
|
|
|
@ -27,11 +27,6 @@ class ExpressionSerializer(serializers.Serializer):
|
|||
expression = serializers.CharField()
|
||||
|
||||
|
||||
class ResultTextSerializer(serializers.Serializer):
|
||||
''' Serializer: Text result of a function call. '''
|
||||
result = serializers.CharField()
|
||||
|
||||
|
||||
class TextSerializer(serializers.Serializer):
|
||||
''' Serializer: Text with references. '''
|
||||
text = serializers.CharField()
|
||||
|
@ -46,20 +41,233 @@ class LibraryItemSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('owner', 'id', 'item_type')
|
||||
|
||||
|
||||
class FunctionArgSerializer(serializers.Serializer):
|
||||
''' Serializer: RSLang function argument type. '''
|
||||
alias = serializers.CharField()
|
||||
typification = serializers.CharField()
|
||||
|
||||
|
||||
class CstParseSerializer(serializers.Serializer):
|
||||
''' Serializer: Constituenta parse result. '''
|
||||
status = serializers.CharField()
|
||||
valueClass = serializers.CharField()
|
||||
typification = serializers.CharField()
|
||||
syntaxTree = serializers.CharField()
|
||||
args = serializers.ListField(
|
||||
child=FunctionArgSerializer()
|
||||
)
|
||||
|
||||
|
||||
class ErrorDescriptionSerializer(serializers.Serializer):
|
||||
''' Serializer: RSError description. '''
|
||||
errorType = serializers.IntegerField()
|
||||
position = serializers.IntegerField()
|
||||
isCritical = serializers.BooleanField()
|
||||
params = serializers.ListField(
|
||||
child=serializers.CharField()
|
||||
)
|
||||
|
||||
class NodeDataSerializer(serializers.Serializer):
|
||||
''' Serializer: Node data. '''
|
||||
dataType = serializers.CharField()
|
||||
value = serializers.CharField()
|
||||
|
||||
|
||||
class ASTNodeSerializer(serializers.Serializer):
|
||||
''' Serializer: Syntax tree node. '''
|
||||
uid = serializers.IntegerField()
|
||||
parent = serializers.IntegerField() # type: ignore
|
||||
typeID = serializers.IntegerField()
|
||||
start = serializers.IntegerField()
|
||||
finish = serializers.IntegerField()
|
||||
data = NodeDataSerializer() # type: ignore
|
||||
|
||||
|
||||
class ExpressionParseSerializer(serializers.Serializer):
|
||||
''' Serializer: RSlang expression parse result. '''
|
||||
parseResult = serializers.BooleanField()
|
||||
syntax = serializers.CharField()
|
||||
typification = serializers.CharField()
|
||||
valueClass = serializers.CharField()
|
||||
astText = serializers.CharField()
|
||||
ast = serializers.ListField(
|
||||
child=ASTNodeSerializer()
|
||||
)
|
||||
errors = serializers.ListField( # type: ignore
|
||||
child=ErrorDescriptionSerializer()
|
||||
)
|
||||
args = serializers.ListField(
|
||||
child=FunctionArgSerializer()
|
||||
)
|
||||
|
||||
|
||||
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: LibraryItem detailed data. '''
|
||||
subscribers = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = LibraryItem
|
||||
fields = '__all__'
|
||||
read_only_fields = ('owner', 'id', 'item_type')
|
||||
|
||||
def to_representation(self, instance: LibraryItem):
|
||||
result = super().to_representation(instance)
|
||||
result['subscribers'] = [item.pk for item in instance.subscribers()]
|
||||
def get_subscribers(self, instance: LibraryItem) -> list[int]:
|
||||
return [item.pk for item in instance.subscribers()]
|
||||
|
||||
|
||||
class ConstituentaSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Constituenta data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Constituenta
|
||||
fields = '__all__'
|
||||
read_only_fields = ('id', 'order', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
|
||||
|
||||
def update(self, instance: Constituenta, validated_data) -> Constituenta:
|
||||
schema = RSForm(instance.schema)
|
||||
definition: Optional[str] = validated_data['definition_raw'] if 'definition_raw' in validated_data else None
|
||||
term: Optional[str] = validated_data['term_raw'] if 'term_raw' in validated_data else None
|
||||
term_changed = False
|
||||
if definition is not None and definition != instance.definition_raw :
|
||||
validated_data['definition_resolved'] = schema.resolver().resolve(definition)
|
||||
if term is not None and term != instance.term_raw:
|
||||
validated_data['term_resolved'] = schema.resolver().resolve(term)
|
||||
if validated_data['term_resolved'] != instance.term_resolved:
|
||||
validated_data['term_forms'] = []
|
||||
term_changed = validated_data['term_resolved'] != instance.term_resolved
|
||||
result: Constituenta = super().update(instance, validated_data)
|
||||
if term_changed:
|
||||
schema.on_term_change([result.alias])
|
||||
result.refresh_from_db()
|
||||
schema.item.save()
|
||||
return result
|
||||
|
||||
|
||||
class CstCreateSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Constituenta creation. '''
|
||||
insert_after = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Constituenta
|
||||
fields = 'alias', 'cst_type', 'convention', 'term_raw', 'definition_raw', 'definition_formal', 'insert_after'
|
||||
|
||||
|
||||
class CstRenameSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Constituenta renaming. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Constituenta
|
||||
fields = 'id', 'alias', 'cst_type'
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = cast(RSForm, self.context['schema'])
|
||||
old_cst = Constituenta.objects.get(pk=self.initial_data['id'])
|
||||
if old_cst.schema != schema.item:
|
||||
raise serializers.ValidationError({
|
||||
'id': f'Изменяемая конституента должна относиться к изменяемой схеме: {schema.item.title}'
|
||||
})
|
||||
if old_cst.alias == self.initial_data['alias']:
|
||||
raise serializers.ValidationError({
|
||||
'alias': f'Имя конституенты должно отличаться от текущего: {self.initial_data["alias"]}'
|
||||
})
|
||||
self.instance = old_cst
|
||||
attrs['schema'] = schema.item
|
||||
attrs['id'] = self.initial_data['id']
|
||||
return attrs
|
||||
|
||||
|
||||
class CstListSerializer(serializers.Serializer):
|
||||
''' Serializer: List of constituents from one origin. '''
|
||||
items = serializers.ListField(
|
||||
child=serializers.IntegerField()
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = self.context['schema']
|
||||
cstList = []
|
||||
for item in attrs['items']:
|
||||
try:
|
||||
cst = Constituenta.objects.get(pk=item)
|
||||
except Constituenta.DoesNotExist as exception:
|
||||
raise serializers.ValidationError(
|
||||
{f"{item}": 'Конституента не существует'}
|
||||
) from exception
|
||||
if cst.schema != schema.item:
|
||||
raise serializers.ValidationError(
|
||||
{'items': f'Конституенты должны относиться к данной схеме: {item}'})
|
||||
cstList.append(cst)
|
||||
attrs['constituents'] = cstList
|
||||
return attrs
|
||||
|
||||
|
||||
class CstMoveSerializer(CstListSerializer):
|
||||
''' Serializer: Change constituenta position. '''
|
||||
move_to = serializers.IntegerField()
|
||||
|
||||
|
||||
class TextPositionSerializer(serializers.Serializer):
|
||||
''' Serializer: Text position. '''
|
||||
start = serializers.IntegerField()
|
||||
finish = serializers.IntegerField()
|
||||
|
||||
|
||||
class ReferenceDataSerializer(serializers.Serializer):
|
||||
''' Serializer: Reference data - Union of all references. '''
|
||||
offset = serializers.IntegerField()
|
||||
nominal = serializers.CharField()
|
||||
entity = serializers.CharField()
|
||||
form = serializers.CharField()
|
||||
|
||||
|
||||
class ReferenceSerializer(serializers.Serializer):
|
||||
''' Serializer: Language reference. '''
|
||||
type = serializers.CharField()
|
||||
data = ReferenceDataSerializer() # type: ignore
|
||||
pos_input = TextPositionSerializer()
|
||||
pos_output = TextPositionSerializer()
|
||||
|
||||
|
||||
class ResolverSerializer(serializers.Serializer):
|
||||
''' Serializer: Resolver results serializer. '''
|
||||
input = serializers.CharField()
|
||||
output = serializers.CharField()
|
||||
refs = serializers.ListField(
|
||||
child=ReferenceSerializer()
|
||||
)
|
||||
|
||||
def to_representation(self, instance: Resolver) -> dict:
|
||||
return {
|
||||
'input': instance.input,
|
||||
'output': instance.output,
|
||||
'refs': [{
|
||||
'type': ref.ref.get_type().value,
|
||||
'data': self._get_reference_data(ref.ref),
|
||||
'resolved': ref.resolved,
|
||||
'pos_input': {
|
||||
'start': ref.pos_input.start,
|
||||
'finish': ref.pos_input.finish
|
||||
},
|
||||
'pos_output': {
|
||||
'start': ref.pos_output.start,
|
||||
'finish': ref.pos_output.finish
|
||||
}
|
||||
} for ref in instance.refs]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_reference_data(ref: Reference) -> dict:
|
||||
if ref.get_type() == ReferenceType.entity:
|
||||
return {
|
||||
'entity': cast(EntityReference, ref).entity,
|
||||
'form': cast(EntityReference, ref).form
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'offset': cast(SyntacticReference, ref).offset,
|
||||
'nominal': cast(SyntacticReference, ref).nominal
|
||||
}
|
||||
|
||||
class PyConceptAdapter:
|
||||
''' RSForm adapter for interacting with pyconcept module. '''
|
||||
def __init__(self, instance: RSForm):
|
||||
|
@ -113,27 +321,54 @@ class PyConceptAdapter:
|
|||
|
||||
class RSFormSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Detailed data for RSForm. '''
|
||||
subscribers = serializers.ListField(
|
||||
child=serializers.IntegerField()
|
||||
)
|
||||
items = serializers.ListField(
|
||||
child=ConstituentaSerializer()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = RSForm
|
||||
model = LibraryItem
|
||||
fields = '__all__'
|
||||
|
||||
def to_representation(self, instance: RSForm):
|
||||
result = LibraryItemDetailsSerializer(instance.item).data
|
||||
def to_representation(self, instance: LibraryItem):
|
||||
result = LibraryItemDetailsSerializer(instance).data
|
||||
schema = RSForm(instance)
|
||||
result['items'] = []
|
||||
for cst in instance.constituents().order_by('order'):
|
||||
for cst in schema.constituents().order_by('order'):
|
||||
result['items'].append(ConstituentaSerializer(cst).data)
|
||||
return result
|
||||
|
||||
|
||||
class RSFormParseSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Detailed data for RSForm including parse. '''
|
||||
class CstDetailsSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Constituenta data including parse. '''
|
||||
parse = CstParseSerializer()
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = RSForm
|
||||
model = Constituenta
|
||||
fields = '__all__'
|
||||
|
||||
def to_representation(self, instance: RSForm):
|
||||
|
||||
class RSFormParseSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Detailed data for RSForm including parse. '''
|
||||
subscribers = serializers.ListField(
|
||||
child=serializers.IntegerField()
|
||||
)
|
||||
items = serializers.ListField(
|
||||
child=CstDetailsSerializer()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = LibraryItem
|
||||
fields = '__all__'
|
||||
|
||||
def to_representation(self, instance: LibraryItem):
|
||||
result = RSFormSerializer(instance).data
|
||||
parse = PyConceptAdapter(instance).parse()
|
||||
parse = PyConceptAdapter(RSForm(instance)).parse()
|
||||
for cst_data in result['items']:
|
||||
cst_data['parse'] = next(
|
||||
cst['parse'] for cst in parse['items']
|
||||
|
@ -302,142 +537,12 @@ class RSFormTRSSerializer(serializers.Serializer):
|
|||
cst.term_raw = ''
|
||||
cst.term_forms = []
|
||||
|
||||
|
||||
class ConstituentaSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Constituenta data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Constituenta
|
||||
fields = '__all__'
|
||||
read_only_fields = ('id', 'order', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
|
||||
|
||||
def update(self, instance: Constituenta, validated_data) -> Constituenta:
|
||||
schema = RSForm(instance.schema)
|
||||
definition: Optional[str] = validated_data['definition_raw'] if 'definition_raw' in validated_data else None
|
||||
term: Optional[str] = validated_data['term_raw'] if 'term_raw' in validated_data else None
|
||||
term_changed = False
|
||||
if definition is not None and definition != instance.definition_raw :
|
||||
validated_data['definition_resolved'] = schema.resolver().resolve(definition)
|
||||
if term is not None and term != instance.term_raw:
|
||||
validated_data['term_resolved'] = schema.resolver().resolve(term)
|
||||
if validated_data['term_resolved'] != instance.term_resolved:
|
||||
validated_data['term_forms'] = []
|
||||
term_changed = validated_data['term_resolved'] != instance.term_resolved
|
||||
result: Constituenta = super().update(instance, validated_data)
|
||||
if term_changed:
|
||||
schema.on_term_change([result.alias])
|
||||
result.refresh_from_db()
|
||||
schema.item.save()
|
||||
return result
|
||||
class ResultTextResponse(serializers.Serializer):
|
||||
''' Serializer: Text result of a function call. '''
|
||||
result = serializers.CharField()
|
||||
|
||||
|
||||
class CstStandaloneSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Constituenta in current context. '''
|
||||
id = serializers.IntegerField()
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Constituenta
|
||||
exclude = ('schema', )
|
||||
|
||||
def validate(self, attrs):
|
||||
try:
|
||||
attrs['object'] = Constituenta.objects.get(pk=attrs['id'])
|
||||
except Constituenta.DoesNotExist as exception:
|
||||
raise serializers.ValidationError({f"{attrs['id']}": 'Конституента не существует'}) from exception
|
||||
return attrs
|
||||
|
||||
|
||||
class CstCreateSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Constituenta creation. '''
|
||||
insert_after = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Constituenta
|
||||
fields = 'alias', 'cst_type', 'convention', 'term_raw', 'definition_raw', 'definition_formal', 'insert_after'
|
||||
|
||||
|
||||
class CstRenameSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Constituenta renaming. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Constituenta
|
||||
fields = 'id', 'alias', 'cst_type'
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = cast(RSForm, self.context['schema'])
|
||||
old_cst = Constituenta.objects.get(pk=self.initial_data['id'])
|
||||
if old_cst.schema != schema.item:
|
||||
raise serializers.ValidationError({
|
||||
'id': f'Изменяемая конституента должна относиться к изменяемой схеме: {schema.item.title}'
|
||||
})
|
||||
if old_cst.alias == self.initial_data['alias']:
|
||||
raise serializers.ValidationError({
|
||||
'alias': f'Имя конституенты должно отличаться от текущего: {self.initial_data["alias"]}'
|
||||
})
|
||||
self.instance = old_cst
|
||||
attrs['schema'] = schema.item
|
||||
attrs['id'] = self.initial_data['id']
|
||||
return attrs
|
||||
|
||||
|
||||
class CstListSerializer(serializers.Serializer):
|
||||
''' Serializer: List of constituents from one origin. '''
|
||||
# TODO: fix schema
|
||||
items = serializers.ListField(
|
||||
child=CstStandaloneSerializer()
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = self.context['schema']
|
||||
cstList = []
|
||||
for item in attrs['items']:
|
||||
cst = item['object']
|
||||
if cst.schema != schema.item:
|
||||
raise serializers.ValidationError(
|
||||
{'items': f'Конституенты должны относиться к данной схеме: {item}'})
|
||||
cstList.append(cst)
|
||||
attrs['constituents'] = cstList
|
||||
return attrs
|
||||
|
||||
|
||||
class CstMoveSerializer(CstListSerializer):
|
||||
''' Serializer: Change constituenta position. '''
|
||||
move_to = serializers.IntegerField()
|
||||
|
||||
|
||||
class ResolverSerializer(serializers.Serializer):
|
||||
''' Serializer: Resolver results serializer. '''
|
||||
# TODO: add schema
|
||||
def to_representation(self, instance: Resolver) -> dict:
|
||||
return {
|
||||
'input': instance.input,
|
||||
'output': instance.output,
|
||||
'refs': [{
|
||||
'type': ref.ref.get_type().value,
|
||||
'data': self._get_reference_data(ref.ref),
|
||||
'resolved': ref.resolved,
|
||||
'pos_input': {
|
||||
'start': ref.pos_input.start,
|
||||
'finish': ref.pos_input.finish
|
||||
},
|
||||
'pos_output': {
|
||||
'start': ref.pos_output.start,
|
||||
'finish': ref.pos_output.finish
|
||||
}
|
||||
} for ref in instance.refs]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_reference_data(ref: Reference) -> dict:
|
||||
if ref.get_type() == ReferenceType.entity:
|
||||
return {
|
||||
'entity': cast(EntityReference, ref).entity,
|
||||
'form': cast(EntityReference, ref).form
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'offset': cast(SyntacticReference, ref).offset,
|
||||
'nominal': cast(SyntacticReference, ref).nominal
|
||||
}
|
||||
class NewCstResponse(serializers.Serializer):
|
||||
''' Serializer: Create cst response. '''
|
||||
new_cst = ConstituentaSerializer()
|
||||
schema = RSFormParseSerializer()
|
||||
|
|
|
@ -80,7 +80,7 @@ class TestLibraryItem(TestCase):
|
|||
testStr = 'Test123'
|
||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM,
|
||||
title=testStr, owner=self.user1, alias='КС1')
|
||||
self.assertEqual(item.get_absolute_url(), f'/api/library/{item.id}/')
|
||||
self.assertEqual(item.get_absolute_url(), f'/api/library/{item.id}')
|
||||
|
||||
def test_create_default(self):
|
||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test')
|
||||
|
|
|
@ -224,19 +224,19 @@ class TestLibraryViewset(APITestCase):
|
|||
|
||||
def test_subscriptions(self):
|
||||
response = self.client.delete(f'/api/library/{self.unowned.id}/unsubscribe')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertFalse(self.user in self.unowned.subscribers())
|
||||
|
||||
response = self.client.post(f'/api/library/{self.unowned.id}/subscribe')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertTrue(self.user in self.unowned.subscribers())
|
||||
|
||||
response = self.client.post(f'/api/library/{self.unowned.id}/subscribe')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertTrue(self.user in self.unowned.subscribers())
|
||||
|
||||
response = self.client.delete(f'/api/library/{self.unowned.id}/unsubscribe')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertFalse(self.user in self.unowned.subscribers())
|
||||
|
||||
|
||||
|
@ -458,14 +458,14 @@ class TestRSFormViewset(APITestCase):
|
|||
|
||||
def test_delete_constituenta(self):
|
||||
schema = self.owned
|
||||
data = json.dumps({'items': [{'id': 1337}]})
|
||||
data = json.dumps({'items': [1337]})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
x1 = Constituenta.objects.create(schema=schema.item, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema.item, alias='X2', cst_type='basic', order=2)
|
||||
data = json.dumps({'items': [{'id': x1.id}]})
|
||||
data = json.dumps({'items': [x1.id]})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||
data=data, content_type='application/json')
|
||||
x2.refresh_from_db()
|
||||
|
@ -477,21 +477,21 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(x2.order, 1)
|
||||
|
||||
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1)
|
||||
data = json.dumps({'items': [{'id': x3.id}]})
|
||||
data = json.dumps({'items': [x3.id]})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_move_constituenta(self):
|
||||
item = self.owned.item
|
||||
data = json.dumps({'items': [{'id': 1337}], 'move_to': 1})
|
||||
data = json.dumps({'items': [1337], 'move_to': 1})
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=2)
|
||||
data = json.dumps({'items': [{'id': x2.id}], 'move_to': 1})
|
||||
data = json.dumps({'items': [x2.id], 'move_to': 1})
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
||||
data=data, content_type='application/json')
|
||||
x1.refresh_from_db()
|
||||
|
@ -502,7 +502,7 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(x2.order, 1)
|
||||
|
||||
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1)
|
||||
data = json.dumps({'items': [{'id': x3.id}], 'move_to': 1})
|
||||
data = json.dumps({'items': [x3.id], 'move_to': 1})
|
||||
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
|
|
@ -10,6 +10,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import api_view
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
from rest_framework import status as c
|
||||
|
||||
import pyconcept
|
||||
from . import models as m
|
||||
|
@ -84,11 +85,14 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
def _get_item(self) -> m.LibraryItem:
|
||||
return cast(m.LibraryItem, self.get_object())
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=s.LibraryItemSerializer,
|
||||
summary='clone item including contents',
|
||||
tags=['Library']
|
||||
tags=['Library'],
|
||||
request=s.LibraryItemSerializer,
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.RSFormParseSerializer,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@transaction.atomic
|
||||
@action(detail=True, methods=['post'], url_path='clone')
|
||||
|
@ -110,14 +114,17 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
clone = s.RSFormTRSSerializer(data=clone_data, context={'load_meta': True})
|
||||
clone.is_valid(raise_exception=True)
|
||||
new_schema = clone.save()
|
||||
return Response(status=201, data=s.RSFormParseSerializer(new_schema).data)
|
||||
return Response(status=404)
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data=s.RSFormParseSerializer(new_schema.item).data
|
||||
)
|
||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||
|
||||
@extend_schema(
|
||||
request=None,
|
||||
responses={200: s.LibraryItemSerializer},
|
||||
summary='claim item',
|
||||
tags=['Library']
|
||||
tags=['Library'],
|
||||
request=None,
|
||||
responses={c.HTTP_200_OK: s.LibraryItemSerializer}
|
||||
)
|
||||
@transaction.atomic
|
||||
@action(detail=True, methods=['post'])
|
||||
|
@ -130,33 +137,36 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
item.owner = self.request.user
|
||||
item.save()
|
||||
m.Subscription.subscribe(user=item.owner, item=item)
|
||||
return Response(status=200, data=s.LibraryItemSerializer(item).data)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.LibraryItemSerializer(item).data
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
request=None,
|
||||
responses={200: None},
|
||||
summary='subscribe to item',
|
||||
tags=['Library']
|
||||
tags=['Library'],
|
||||
request=None,
|
||||
responses={c.HTTP_204_NO_CONTENT: None}
|
||||
)
|
||||
@action(detail=True, methods=['post'])
|
||||
def subscribe(self, request, pk):
|
||||
''' Endpoint: Subscribe current user to item. '''
|
||||
item = self._get_item()
|
||||
m.Subscription.subscribe(user=self.request.user, item=item)
|
||||
return Response(status=200)
|
||||
return Response(status=c.HTTP_204_NO_CONTENT)
|
||||
|
||||
@extend_schema(
|
||||
request=None,
|
||||
responses={200: None},
|
||||
summary='unsubscribe from item',
|
||||
tags=['Library']
|
||||
tags=['Library'],
|
||||
request=None,
|
||||
responses={c.HTTP_204_NO_CONTENT: None},
|
||||
)
|
||||
@action(detail=True, methods=['delete'])
|
||||
def unsubscribe(self, request, pk):
|
||||
''' Endpoint: Unsubscribe current user from item. '''
|
||||
item = self._get_item()
|
||||
m.Subscription.unsubscribe(user=self.request.user, item=item)
|
||||
return Response(status=200)
|
||||
return Response(status=c.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@extend_schema(tags=['RSForm'])
|
||||
|
@ -178,11 +188,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
permission_classes = [permissions.AllowAny]
|
||||
return [permission() for permission in permission_classes]
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=s.CstCreateSerializer,
|
||||
summary='create constituenta',
|
||||
tags=['Constituenta']
|
||||
tags=['Constituenta'],
|
||||
request=s.CstCreateSerializer,
|
||||
responses={c.HTTP_201_CREATED: s.NewCstResponse}
|
||||
)
|
||||
@action(detail=True, methods=['post'], url_path='cst-create')
|
||||
def cst_create(self, request, pk):
|
||||
|
@ -193,18 +203,21 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
data = serializer.validated_data
|
||||
new_cst = schema.create_cst(data, data['insert_after'] if 'insert_after' in data else None)
|
||||
schema.item.refresh_from_db()
|
||||
response = Response(status=201, data={
|
||||
'new_cst': s.ConstituentaSerializer(new_cst).data,
|
||||
'schema': s.RSFormParseSerializer(schema).data
|
||||
})
|
||||
response = Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_cst': s.ConstituentaSerializer(new_cst).data,
|
||||
'schema': s.RSFormParseSerializer(schema.item).data
|
||||
}
|
||||
)
|
||||
response['Location'] = new_cst.get_absolute_url()
|
||||
return response
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=s.CstRenameSerializer,
|
||||
summary='rename constituenta',
|
||||
tags=['Constituenta']
|
||||
tags=['Constituenta'],
|
||||
request=s.CstRenameSerializer,
|
||||
responses={c.HTTP_200_OK: s.NewCstResponse}
|
||||
)
|
||||
@transaction.atomic
|
||||
@action(detail=True, methods=['patch'], url_path='cst-rename')
|
||||
|
@ -219,16 +232,19 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
schema.apply_mapping(mapping, change_aliases=False)
|
||||
schema.item.refresh_from_db()
|
||||
cst = m.Constituenta.objects.get(pk=serializer.validated_data['id'])
|
||||
return Response(status=200, data={
|
||||
'new_cst': s.ConstituentaSerializer(cst).data,
|
||||
'schema': s.RSFormParseSerializer(schema).data
|
||||
})
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data={
|
||||
'new_cst': s.ConstituentaSerializer(cst).data,
|
||||
'schema': s.RSFormParseSerializer(schema.item).data
|
||||
}
|
||||
)
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=s.CstListSerializer,
|
||||
summary='delete constituents',
|
||||
tags=['Constituenta']
|
||||
tags=['Constituenta'],
|
||||
request=s.CstListSerializer,
|
||||
responses={c.HTTP_202_ACCEPTED: s.RSFormParseSerializer}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-multidelete')
|
||||
def cst_multidelete(self, request, pk):
|
||||
|
@ -238,13 +254,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
serializer.is_valid(raise_exception=True)
|
||||
schema.delete_cst(serializer.validated_data['constituents'])
|
||||
schema.item.refresh_from_db()
|
||||
return Response(status=202, data=s.RSFormParseSerializer(schema).data)
|
||||
return Response(
|
||||
status=c.HTTP_202_ACCEPTED,
|
||||
data=s.RSFormParseSerializer(schema.item).data
|
||||
)
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=s.CstMoveSerializer,
|
||||
summary='move constituenta',
|
||||
tags=['Constituenta']
|
||||
tags=['Constituenta'],
|
||||
request=s.CstMoveSerializer,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
||||
def cst_moveto(self, request, pk):
|
||||
|
@ -254,26 +273,32 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
serializer.is_valid(raise_exception=True)
|
||||
schema.move_cst(serializer.validated_data['constituents'], serializer.validated_data['move_to'])
|
||||
schema.item.refresh_from_db()
|
||||
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.RSFormParseSerializer(schema.item).data
|
||||
)
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=None,
|
||||
summary='reset aliases, update expressions and references',
|
||||
tags=['RSForm']
|
||||
tags=['RSForm'],
|
||||
request=None,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
||||
def reset_aliases(self, request, pk):
|
||||
''' Endpoint: Recreate all aliases based on order. '''
|
||||
schema = self._get_schema()
|
||||
schema.reset_aliases()
|
||||
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.RSFormParseSerializer(schema.item).data
|
||||
)
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=s.RSFormUploadSerializer,
|
||||
summary='load data from TRS file',
|
||||
tags=['RSForm']
|
||||
tags=['RSForm'],
|
||||
request=s.RSFormUploadSerializer,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='load-trs')
|
||||
def load_trs(self, request, pk):
|
||||
|
@ -288,38 +313,47 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': load_metadata})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema = serializer.save()
|
||||
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.RSFormParseSerializer(schema.item).data
|
||||
)
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=None,
|
||||
summary='get all constituents data from DB',
|
||||
tags=['RSForm']
|
||||
tags=['RSForm'],
|
||||
request=None,
|
||||
responses={c.HTTP_200_OK: s.RSFormSerializer}
|
||||
)
|
||||
@action(detail=True, methods=['get'])
|
||||
def contents(self, request, pk):
|
||||
''' Endpoint: View schema db contents (including constituents). '''
|
||||
schema = s.RSFormSerializer(self._get_schema()).data
|
||||
return Response(schema)
|
||||
schema = s.RSFormSerializer(self.get_object())
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=schema.data
|
||||
)
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=None,
|
||||
summary='get all constituents data and parses',
|
||||
tags=['RSForm']
|
||||
tags=['RSForm'],
|
||||
request=None,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
)
|
||||
@action(detail=True, methods=['get'])
|
||||
def details(self, request, pk):
|
||||
''' Endpoint: Detailed schema view including statuses and parse. '''
|
||||
schema = self._get_schema()
|
||||
serializer = s.RSFormParseSerializer(schema)
|
||||
return Response(serializer.data)
|
||||
serializer = s.RSFormParseSerializer(schema.item)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=serializer.data
|
||||
)
|
||||
|
||||
# TODO: response schema
|
||||
@extend_schema(
|
||||
request=s.ExpressionSerializer,
|
||||
summary='check RSLang expression',
|
||||
tags=['RSForm', 'Functions']
|
||||
tags=['RSForm', 'FormalLanguage'],
|
||||
request=s.ExpressionSerializer,
|
||||
responses={c.HTTP_200_OK: s.ExpressionParseSerializer},
|
||||
)
|
||||
@action(detail=True, methods=['post'])
|
||||
def check(self, request, pk):
|
||||
|
@ -329,13 +363,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
expression = serializer.validated_data['expression']
|
||||
schema = s.PyConceptAdapter(self._get_schema())
|
||||
result = pyconcept.check_expression(json.dumps(schema.data), expression)
|
||||
return Response(json.loads(result))
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=json.loads(result)
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
request=s.TextSerializer,
|
||||
responses={200: s.ResolverSerializer},
|
||||
summary='resolve text with references',
|
||||
tags=['RSForm', 'Functions']
|
||||
tags=['RSForm', 'NaturalLanguage'],
|
||||
request=s.TextSerializer,
|
||||
responses={c.HTTP_200_OK: s.ResolverSerializer}
|
||||
)
|
||||
@action(detail=True, methods=['post'])
|
||||
def resolve(self, request, pk):
|
||||
|
@ -345,13 +382,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
text = serializer.validated_data['text']
|
||||
resolver = self._get_schema().resolver()
|
||||
resolver.resolve(text)
|
||||
return Response(status=200, data=s.ResolverSerializer(resolver).data)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.ResolverSerializer(resolver).data
|
||||
)
|
||||
|
||||
# TODO: create a proper file response schema
|
||||
@extend_schema(
|
||||
responses={(200, 'application/zip'): bytes},
|
||||
summary='export as TRS file',
|
||||
tags=['RSForm']
|
||||
tags=['RSForm'],
|
||||
request=None,
|
||||
responses={(c.HTTP_200_OK, 'application/zip'): bytes}
|
||||
)
|
||||
@action(detail=True, methods=['get'], url_path='export-trs')
|
||||
def export_trs(self, request, pk):
|
||||
|
@ -376,7 +416,9 @@ class TrsImportView(views.APIView):
|
|||
|
||||
@extend_schema(
|
||||
summary='import TRS file into RSForm',
|
||||
tags=['RSForm']
|
||||
tags=['RSForm'],
|
||||
request=s.FileSerializer,
|
||||
responses={c.HTTP_201_CREATED: s.LibraryItemSerializer}
|
||||
)
|
||||
def post(self, request):
|
||||
data = utils.read_trs(request.FILES['file'].file)
|
||||
|
@ -388,12 +430,17 @@ class TrsImportView(views.APIView):
|
|||
serializer.is_valid(raise_exception=True)
|
||||
schema = serializer.save()
|
||||
result = s.LibraryItemSerializer(schema.item)
|
||||
return Response(status=201, data=result.data)
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data=result.data
|
||||
)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='create new RSForm empty or from file',
|
||||
tags=['RSForm']
|
||||
tags=['RSForm'],
|
||||
request=s.LibraryItemSerializer,
|
||||
responses={c.HTTP_201_CREATED: s.LibraryItemSerializer}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
def create_rsform(request):
|
||||
|
@ -419,7 +466,10 @@ def create_rsform(request):
|
|||
serializer.is_valid(raise_exception=True)
|
||||
schema = serializer.save()
|
||||
result = s.LibraryItemSerializer(schema.item)
|
||||
return Response(status=201, data=result.data)
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data=result.data
|
||||
)
|
||||
|
||||
def _prepare_rsform_data(data: dict, request, owner: m.User):
|
||||
data['owner'] = owner
|
||||
|
@ -443,11 +493,12 @@ def _prepare_rsform_data(data: dict, request, owner: m.User):
|
|||
data['is_canonical'] = is_canonical
|
||||
|
||||
|
||||
# TODO: define schema for response
|
||||
@extend_schema(
|
||||
request=s.ExpressionSerializer,
|
||||
summary='RS expression into Syntax Tree',
|
||||
tags=['Functions']
|
||||
tags=['FormalLanguage'],
|
||||
request=s.ExpressionSerializer,
|
||||
responses={c.HTTP_200_OK: s.ExpressionParseSerializer},
|
||||
auth=None
|
||||
)
|
||||
@api_view(['POST'])
|
||||
def parse_expression(request):
|
||||
|
@ -456,14 +507,18 @@ def parse_expression(request):
|
|||
serializer.is_valid(raise_exception=True)
|
||||
expression = serializer.validated_data['expression']
|
||||
result = pyconcept.parse_expression(expression)
|
||||
return Response(json.loads(result))
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=json.loads(result)
|
||||
)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
request=s.ExpressionSerializer,
|
||||
responses={200: s.ResultTextSerializer},
|
||||
summary='Unicode syntax to ASCII TeX',
|
||||
tags=['Functions']
|
||||
tags=['FormalLanguage'],
|
||||
request=s.ExpressionSerializer,
|
||||
responses={c.HTTP_200_OK: s.ResultTextResponse},
|
||||
auth=None
|
||||
)
|
||||
@api_view(['POST'])
|
||||
def convert_to_ascii(request):
|
||||
|
@ -476,10 +531,11 @@ def convert_to_ascii(request):
|
|||
|
||||
|
||||
@extend_schema(
|
||||
request=s.ExpressionSerializer,
|
||||
responses={200: s.ResultTextSerializer},
|
||||
summary='ASCII TeX syntax to Unicode symbols',
|
||||
tags=['Functions']
|
||||
tags=['FormalLanguage'],
|
||||
request=s.ExpressionSerializer,
|
||||
responses={200: s.ResultTextResponse},
|
||||
auth=None
|
||||
)
|
||||
@api_view(['POST'])
|
||||
def convert_to_math(request):
|
||||
|
|
|
@ -7,9 +7,15 @@ from apps.rsform.models import Subscription
|
|||
from . import models
|
||||
|
||||
|
||||
class NonFieldErrorSerializer(serializers.Serializer):
|
||||
''' Serializer: list of non-field errors. '''
|
||||
non_field_errors = serializers.ListField(
|
||||
child=serializers.CharField()
|
||||
)
|
||||
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
''' Serializer: User authentification by login/password. '''
|
||||
# TODO: declare schema
|
||||
username = serializers.CharField(
|
||||
label='Имя пользователя',
|
||||
write_only=True
|
||||
|
@ -44,7 +50,13 @@ class LoginSerializer(serializers.Serializer):
|
|||
|
||||
class AuthSerializer(serializers.Serializer):
|
||||
''' Serializer: Authentication data. '''
|
||||
# TODO: declare schema
|
||||
id = serializers.IntegerField()
|
||||
username = serializers.CharField()
|
||||
is_staff = serializers.BooleanField()
|
||||
subscriptions = serializers.ListField(
|
||||
child=serializers.IntegerField()
|
||||
)
|
||||
|
||||
def to_representation(self, instance: models.User) -> dict:
|
||||
if instance.is_anonymous:
|
||||
return {
|
||||
|
|
|
@ -1,40 +1,52 @@
|
|||
''' REST API: User profile and Authentification. '''
|
||||
from django.contrib.auth import login, logout
|
||||
|
||||
from rest_framework import status, permissions, views, generics
|
||||
from rest_framework import status as c
|
||||
from rest_framework import permissions, views, generics
|
||||
from rest_framework.response import Response
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
from . import serializers
|
||||
from . import models
|
||||
from . import serializers as s
|
||||
from . import models as m
|
||||
|
||||
|
||||
@extend_schema(tags=['Auth'])
|
||||
@extend_schema_view()
|
||||
class LoginAPIView(views.APIView):
|
||||
''' Endpoint: Login via username + password. '''
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
|
||||
@extend_schema(
|
||||
summary='login user',
|
||||
tags=['Auth'],
|
||||
request=s.LoginSerializer,
|
||||
responses={
|
||||
c.HTTP_202_ACCEPTED: None,
|
||||
c.HTTP_400_BAD_REQUEST: s.NonFieldErrorSerializer
|
||||
}
|
||||
)
|
||||
def post(self, request):
|
||||
serializer = serializers.LoginSerializer(
|
||||
serializer = s.LoginSerializer(
|
||||
data=self.request.data,
|
||||
context={'request': self.request}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = serializer.validated_data['user']
|
||||
login(request, user)
|
||||
return Response(None, status=status.HTTP_202_ACCEPTED)
|
||||
return Response(None, status=c.HTTP_202_ACCEPTED)
|
||||
|
||||
|
||||
@extend_schema(tags=['Auth'])
|
||||
@extend_schema_view()
|
||||
class LogoutAPIView(views.APIView):
|
||||
''' Endpoint: Logout. '''
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
@extend_schema(
|
||||
summary='logout current user',
|
||||
tags=['Auth'],
|
||||
request=None,
|
||||
responses={c.HTTP_204_NO_CONTENT: None}
|
||||
)
|
||||
def post(self, request):
|
||||
logout(request)
|
||||
return Response(None, status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(None, status=c.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@extend_schema(tags=['User'])
|
||||
|
@ -42,7 +54,7 @@ class LogoutAPIView(views.APIView):
|
|||
class SignupAPIView(generics.CreateAPIView):
|
||||
''' Endpoint: Register user. '''
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
serializer_class = serializers.SignupSerializer
|
||||
serializer_class = s.SignupSerializer
|
||||
|
||||
|
||||
@extend_schema(tags=['Auth'])
|
||||
|
@ -50,7 +62,7 @@ class SignupAPIView(generics.CreateAPIView):
|
|||
class AuthAPIView(generics.RetrieveAPIView):
|
||||
''' Endpoint: Current user info. '''
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.AuthSerializer
|
||||
serializer_class = s.AuthSerializer
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
@ -61,10 +73,10 @@ class AuthAPIView(generics.RetrieveAPIView):
|
|||
class ActiveUsersView(generics.ListAPIView):
|
||||
''' Endpoint: Get list of active users. '''
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.UserSerializer
|
||||
serializer_class = s.UserSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return models.User.objects.filter(is_active=True)
|
||||
return m.User.objects.filter(is_active=True)
|
||||
|
||||
|
||||
@extend_schema(tags=['User'])
|
||||
|
@ -72,14 +84,12 @@ class ActiveUsersView(generics.ListAPIView):
|
|||
class UserProfileAPIView(generics.RetrieveUpdateAPIView):
|
||||
''' Endpoint: User profile. '''
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
serializer_class = serializers.UserSerializer
|
||||
serializer_class = s.UserSerializer
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
|
||||
@extend_schema(tags=['Auth'])
|
||||
@extend_schema_view()
|
||||
class UpdatePassword(views.APIView):
|
||||
''' Endpoint: Change password for current user. '''
|
||||
permission_classes = (permissions.IsAuthenticated, )
|
||||
|
@ -87,17 +97,25 @@ class UpdatePassword(views.APIView):
|
|||
def get_object(self, queryset=None):
|
||||
return self.request.user
|
||||
|
||||
@extend_schema(
|
||||
description='change current user password',
|
||||
tags=['Auth'],
|
||||
request=s.ChangePasswordSerializer,
|
||||
responses={
|
||||
c.HTTP_204_NO_CONTENT: None,
|
||||
c.HTTP_400_BAD_REQUEST: None
|
||||
}
|
||||
)
|
||||
def patch(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
serializer = serializers.ChangePasswordSerializer(data=request.data)
|
||||
serializer = s.ChangePasswordSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
old_password = serializer.data.get("old_password")
|
||||
if not self.object.check_password(old_password):
|
||||
return Response({"old_password": ["Wrong password."]},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
status=c.HTTP_400_BAD_REQUEST)
|
||||
# Note: set_password also hashes the password that the user will get
|
||||
self.object.set_password(serializer.data.get("new_password"))
|
||||
self.object.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(status=c.HTTP_204_NO_CONTENT)
|
||||
return Response(serializer.errors, status=c.HTTP_400_BAD_REQUEST)
|
||||
|
|
|
@ -86,9 +86,10 @@ MIDDLEWARE = [
|
|||
]
|
||||
|
||||
ROOT_URLCONF = 'project.urls'
|
||||
LOGIN_URL = '/admin/login/'
|
||||
LOGIN_URL = '/admin/login'
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
LOGOUT_REDIRECT_URL = '/'
|
||||
APPEND_SLASH = False
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
|
@ -140,6 +141,20 @@ SPECTACULAR_SETTINGS = {
|
|||
'DESCRIPTION': 'Портал для работы с экспликациями концептуальных схем',
|
||||
'VERSION': '0.1.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
|
||||
'COMPONENT_SPLIT_PATCH': True,
|
||||
'COMPONENT_SPLIT_REQUEST': True,
|
||||
|
||||
'SERVE_PERMISSIONS': ['rest_framework.permissions.AllowAny'],
|
||||
'SERVE_AUTHENTICATION': None,
|
||||
|
||||
'DISABLE_ERRORS_AND_WARNINGS': False,
|
||||
|
||||
"SWAGGER_UI_SETTINGS": {
|
||||
"deepLinking": True,
|
||||
"persistAuthorization": True,
|
||||
"withCredentials": True
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ urlpatterns = [
|
|||
path('admin', admin.site.urls),
|
||||
path('api/', include('apps.rsform.urls')),
|
||||
path('users/', include('apps.users.urls')),
|
||||
path('docs', SpectacularSwaggerView.as_view(), name='docs'),
|
||||
path('docs/', SpectacularSwaggerView.as_view(), name='docs'),
|
||||
path('schema', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('redoc', SpectacularRedocView.as_view()),
|
||||
path('', lambda: redirect('docs', permanent=True)),
|
||||
path('', lambda: redirect('docs/', permanent=True)),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
|
|
@ -67,9 +67,8 @@ extends IConstituentaMeta {
|
|||
}
|
||||
}
|
||||
|
||||
export interface IConstituentaID extends Pick<IConstituentaMeta, 'id'>{}
|
||||
export interface IConstituentaList {
|
||||
items: IConstituentaID[]
|
||||
items: number[]
|
||||
}
|
||||
|
||||
export interface ICstCreateData
|
||||
|
|
|
@ -64,7 +64,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
}, -1);
|
||||
const target = Math.max(0, currentIndex - 1) + 1
|
||||
const data = {
|
||||
items: selected.map(id => ({ id: id })),
|
||||
items: selected,
|
||||
move_to: target
|
||||
}
|
||||
cstMoveTo(data, () => {
|
||||
|
@ -95,7 +95,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
}, -1);
|
||||
const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
||||
const data: ICstMovetoData = {
|
||||
items: selected.map(id => ({ id: id })),
|
||||
items: selected,
|
||||
move_to: target
|
||||
}
|
||||
cstMoveTo(data, () => {
|
||||
|
|
|
@ -180,7 +180,7 @@ function RSTabs() {
|
|||
return;
|
||||
}
|
||||
const data = {
|
||||
items: deleted.map(id => ({ id: id }))
|
||||
items: deleted
|
||||
};
|
||||
let activeIndex = schema.items.findIndex(cst => cst.id === activeID);
|
||||
cstDelete(data, () => {
|
||||
|
|
|
@ -220,7 +220,7 @@ export function postNewConstituenta(schema: string, request: FrontExchange<ICstC
|
|||
|
||||
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
|
||||
AxiosPatch({
|
||||
title: `Delete Constituents for RSForm id=${schema}: ${request.data.items.map(item => String(item.id)).join(' ')}`,
|
||||
title: `Delete Constituents for RSForm id=${schema}: ${request.data.items.map(item => String(item)).join(' ')}`,
|
||||
endpoint: `/api/rsforms/${schema}/cst-multidelete`,
|
||||
request: request
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ export const urls = {
|
|||
|
||||
gitrepo: 'https://github.com/IRBorisov/ConceptPortal',
|
||||
mailportal: 'mailto:portal@acconcept.ru',
|
||||
restapi: 'https://api.portal.acconcept.ru/docs'
|
||||
restapi: 'https://api.portal.acconcept.ru/docs/'
|
||||
};
|
||||
|
||||
export const resources = {
|
||||
|
|
|
@ -14,8 +14,8 @@ function RunServer() {
|
|||
BackendRun
|
||||
FrontendRun
|
||||
Start-Sleep -Seconds 1
|
||||
Start-Process "http://localhost:8000/"
|
||||
Start-Process "http://localhost:3000/"
|
||||
Start-Process "http://localhost:8000"
|
||||
Start-Process "http://localhost:3000"
|
||||
}
|
||||
|
||||
function BackendRun() {
|
||||
|
|
|
@ -8,4 +8,5 @@ git pull
|
|||
|
||||
docker compose --file "${COMPOSE_FILE}" up --build --detach
|
||||
docker image prune --all --force
|
||||
sleep 5
|
||||
docker compose --file "${COMPOSE_FILE}" restart
|
Loading…
Reference in New Issue
Block a user