mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-27 05:20:36 +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):
|
class ConstituentaAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Constituenta. '''
|
''' Admin model: Constituenta. '''
|
||||||
|
ordering = ['schema', 'order']
|
||||||
|
list_display = ['schema', 'alias', 'term_resolved', 'definition_resolved']
|
||||||
|
search_fields = ['term_resolved', 'definition_resolved']
|
||||||
|
|
||||||
class LibraryAdmin(admin.ModelAdmin):
|
class LibraryAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: LibraryItem. '''
|
''' 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):
|
class SubscriptionAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Subscriptions. '''
|
''' 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)
|
admin.site.register(models.Constituenta, ConstituentaAdmin)
|
||||||
|
|
|
@ -122,7 +122,7 @@ class LibraryItem(Model):
|
||||||
return f'{self.title}'
|
return f'{self.title}'
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return f'/api/library/{self.pk}/'
|
return f'/api/library/{self.pk}'
|
||||||
|
|
||||||
def subscribers(self) -> list[User]:
|
def subscribers(self) -> list[User]:
|
||||||
''' Get all subscribers for this item . '''
|
''' Get all subscribers for this item . '''
|
||||||
|
|
|
@ -27,11 +27,6 @@ class ExpressionSerializer(serializers.Serializer):
|
||||||
expression = serializers.CharField()
|
expression = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
class ResultTextSerializer(serializers.Serializer):
|
|
||||||
''' Serializer: Text result of a function call. '''
|
|
||||||
result = serializers.CharField()
|
|
||||||
|
|
||||||
|
|
||||||
class TextSerializer(serializers.Serializer):
|
class TextSerializer(serializers.Serializer):
|
||||||
''' Serializer: Text with references. '''
|
''' Serializer: Text with references. '''
|
||||||
text = serializers.CharField()
|
text = serializers.CharField()
|
||||||
|
@ -46,20 +41,233 @@ class LibraryItemSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = ('owner', 'id', 'item_type')
|
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):
|
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: LibraryItem detailed data. '''
|
''' Serializer: LibraryItem detailed data. '''
|
||||||
|
subscribers = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
model = LibraryItem
|
model = LibraryItem
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = ('owner', 'id', 'item_type')
|
read_only_fields = ('owner', 'id', 'item_type')
|
||||||
|
|
||||||
def to_representation(self, instance: LibraryItem):
|
def get_subscribers(self, instance: LibraryItem) -> list[int]:
|
||||||
result = super().to_representation(instance)
|
return [item.pk for item in instance.subscribers()]
|
||||||
result['subscribers'] = [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
|
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:
|
class PyConceptAdapter:
|
||||||
''' RSForm adapter for interacting with pyconcept module. '''
|
''' RSForm adapter for interacting with pyconcept module. '''
|
||||||
def __init__(self, instance: RSForm):
|
def __init__(self, instance: RSForm):
|
||||||
|
@ -113,27 +321,54 @@ class PyConceptAdapter:
|
||||||
|
|
||||||
class RSFormSerializer(serializers.ModelSerializer):
|
class RSFormSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: Detailed data for RSForm. '''
|
''' Serializer: Detailed data for RSForm. '''
|
||||||
|
subscribers = serializers.ListField(
|
||||||
|
child=serializers.IntegerField()
|
||||||
|
)
|
||||||
|
items = serializers.ListField(
|
||||||
|
child=ConstituentaSerializer()
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
model = RSForm
|
model = LibraryItem
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
def to_representation(self, instance: RSForm):
|
def to_representation(self, instance: LibraryItem):
|
||||||
result = LibraryItemDetailsSerializer(instance.item).data
|
result = LibraryItemDetailsSerializer(instance).data
|
||||||
|
schema = RSForm(instance)
|
||||||
result['items'] = []
|
result['items'] = []
|
||||||
for cst in instance.constituents().order_by('order'):
|
for cst in schema.constituents().order_by('order'):
|
||||||
result['items'].append(ConstituentaSerializer(cst).data)
|
result['items'].append(ConstituentaSerializer(cst).data)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class RSFormParseSerializer(serializers.ModelSerializer):
|
class CstDetailsSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: Detailed data for RSForm including parse. '''
|
''' Serializer: Constituenta data including parse. '''
|
||||||
|
parse = CstParseSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' serializer metadata. '''
|
''' 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
|
result = RSFormSerializer(instance).data
|
||||||
parse = PyConceptAdapter(instance).parse()
|
parse = PyConceptAdapter(RSForm(instance)).parse()
|
||||||
for cst_data in result['items']:
|
for cst_data in result['items']:
|
||||||
cst_data['parse'] = next(
|
cst_data['parse'] = next(
|
||||||
cst['parse'] for cst in parse['items']
|
cst['parse'] for cst in parse['items']
|
||||||
|
@ -302,142 +537,12 @@ class RSFormTRSSerializer(serializers.Serializer):
|
||||||
cst.term_raw = ''
|
cst.term_raw = ''
|
||||||
cst.term_forms = []
|
cst.term_forms = []
|
||||||
|
|
||||||
|
class ResultTextResponse(serializers.Serializer):
|
||||||
class ConstituentaSerializer(serializers.ModelSerializer):
|
''' Serializer: Text result of a function call. '''
|
||||||
''' Serializer: Constituenta data. '''
|
result = serializers.CharField()
|
||||||
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 CstStandaloneSerializer(serializers.ModelSerializer):
|
class NewCstResponse(serializers.Serializer):
|
||||||
''' Serializer: Constituenta in current context. '''
|
''' Serializer: Create cst response. '''
|
||||||
id = serializers.IntegerField()
|
new_cst = ConstituentaSerializer()
|
||||||
|
schema = RSFormParseSerializer()
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ class TestLibraryItem(TestCase):
|
||||||
testStr = 'Test123'
|
testStr = 'Test123'
|
||||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM,
|
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM,
|
||||||
title=testStr, owner=self.user1, alias='КС1')
|
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):
|
def test_create_default(self):
|
||||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test')
|
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test')
|
||||||
|
|
|
@ -224,19 +224,19 @@ class TestLibraryViewset(APITestCase):
|
||||||
|
|
||||||
def test_subscriptions(self):
|
def test_subscriptions(self):
|
||||||
response = self.client.delete(f'/api/library/{self.unowned.id}/unsubscribe')
|
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())
|
self.assertFalse(self.user in self.unowned.subscribers())
|
||||||
|
|
||||||
response = self.client.post(f'/api/library/{self.unowned.id}/subscribe')
|
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())
|
self.assertTrue(self.user in self.unowned.subscribers())
|
||||||
|
|
||||||
response = self.client.post(f'/api/library/{self.unowned.id}/subscribe')
|
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())
|
self.assertTrue(self.user in self.unowned.subscribers())
|
||||||
|
|
||||||
response = self.client.delete(f'/api/library/{self.unowned.id}/unsubscribe')
|
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())
|
self.assertFalse(self.user in self.unowned.subscribers())
|
||||||
|
|
||||||
|
|
||||||
|
@ -458,14 +458,14 @@ class TestRSFormViewset(APITestCase):
|
||||||
|
|
||||||
def test_delete_constituenta(self):
|
def test_delete_constituenta(self):
|
||||||
schema = self.owned
|
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',
|
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
x1 = Constituenta.objects.create(schema=schema.item, alias='X1', cst_type='basic', order=1)
|
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)
|
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',
|
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
x2.refresh_from_db()
|
x2.refresh_from_db()
|
||||||
|
@ -477,21 +477,21 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
|
|
||||||
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', 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',
|
response = self.client.patch(f'/api/rsforms/{schema.item.id}/cst-multidelete',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_move_constituenta(self):
|
def test_move_constituenta(self):
|
||||||
item = self.owned.item
|
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',
|
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1)
|
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)
|
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',
|
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
x1.refresh_from_db()
|
x1.refresh_from_db()
|
||||||
|
@ -502,7 +502,7 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
|
|
||||||
x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', 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',
|
response = self.client.patch(f'/api/rsforms/{item.id}/cst-moveto',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 400)
|
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.response import Response
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
from rest_framework import status as c
|
||||||
|
|
||||||
import pyconcept
|
import pyconcept
|
||||||
from . import models as m
|
from . import models as m
|
||||||
|
@ -84,11 +85,14 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
def _get_item(self) -> m.LibraryItem:
|
def _get_item(self) -> m.LibraryItem:
|
||||||
return cast(m.LibraryItem, self.get_object())
|
return cast(m.LibraryItem, self.get_object())
|
||||||
|
|
||||||
# TODO: response schema
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=s.LibraryItemSerializer,
|
|
||||||
summary='clone item including contents',
|
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
|
@transaction.atomic
|
||||||
@action(detail=True, methods=['post'], url_path='clone')
|
@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 = s.RSFormTRSSerializer(data=clone_data, context={'load_meta': True})
|
||||||
clone.is_valid(raise_exception=True)
|
clone.is_valid(raise_exception=True)
|
||||||
new_schema = clone.save()
|
new_schema = clone.save()
|
||||||
return Response(status=201, data=s.RSFormParseSerializer(new_schema).data)
|
return Response(
|
||||||
return Response(status=404)
|
status=c.HTTP_201_CREATED,
|
||||||
|
data=s.RSFormParseSerializer(new_schema.item).data
|
||||||
|
)
|
||||||
|
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=None,
|
|
||||||
responses={200: s.LibraryItemSerializer},
|
|
||||||
summary='claim item',
|
summary='claim item',
|
||||||
tags=['Library']
|
tags=['Library'],
|
||||||
|
request=None,
|
||||||
|
responses={c.HTTP_200_OK: s.LibraryItemSerializer}
|
||||||
)
|
)
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
|
@ -130,33 +137,36 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
item.owner = self.request.user
|
item.owner = self.request.user
|
||||||
item.save()
|
item.save()
|
||||||
m.Subscription.subscribe(user=item.owner, item=item)
|
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(
|
@extend_schema(
|
||||||
request=None,
|
|
||||||
responses={200: None},
|
|
||||||
summary='subscribe to item',
|
summary='subscribe to item',
|
||||||
tags=['Library']
|
tags=['Library'],
|
||||||
|
request=None,
|
||||||
|
responses={c.HTTP_204_NO_CONTENT: None}
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
def subscribe(self, request, pk):
|
def subscribe(self, request, pk):
|
||||||
''' Endpoint: Subscribe current user to item. '''
|
''' Endpoint: Subscribe current user to item. '''
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
m.Subscription.subscribe(user=self.request.user, item=item)
|
m.Subscription.subscribe(user=self.request.user, item=item)
|
||||||
return Response(status=200)
|
return Response(status=c.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=None,
|
|
||||||
responses={200: None},
|
|
||||||
summary='unsubscribe from item',
|
summary='unsubscribe from item',
|
||||||
tags=['Library']
|
tags=['Library'],
|
||||||
|
request=None,
|
||||||
|
responses={c.HTTP_204_NO_CONTENT: None},
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=['delete'])
|
@action(detail=True, methods=['delete'])
|
||||||
def unsubscribe(self, request, pk):
|
def unsubscribe(self, request, pk):
|
||||||
''' Endpoint: Unsubscribe current user from item. '''
|
''' Endpoint: Unsubscribe current user from item. '''
|
||||||
item = self._get_item()
|
item = self._get_item()
|
||||||
m.Subscription.unsubscribe(user=self.request.user, item=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'])
|
@extend_schema(tags=['RSForm'])
|
||||||
|
@ -178,11 +188,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
return [permission() for permission in permission_classes]
|
return [permission() for permission in permission_classes]
|
||||||
|
|
||||||
# TODO: response schema
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=s.CstCreateSerializer,
|
|
||||||
summary='create constituenta',
|
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')
|
@action(detail=True, methods=['post'], url_path='cst-create')
|
||||||
def cst_create(self, request, pk):
|
def cst_create(self, request, pk):
|
||||||
|
@ -193,18 +203,21 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
new_cst = schema.create_cst(data, data['insert_after'] if 'insert_after' in data else None)
|
new_cst = schema.create_cst(data, data['insert_after'] if 'insert_after' in data else None)
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
response = Response(status=201, data={
|
response = Response(
|
||||||
|
status=c.HTTP_201_CREATED,
|
||||||
|
data={
|
||||||
'new_cst': s.ConstituentaSerializer(new_cst).data,
|
'new_cst': s.ConstituentaSerializer(new_cst).data,
|
||||||
'schema': s.RSFormParseSerializer(schema).data
|
'schema': s.RSFormParseSerializer(schema.item).data
|
||||||
})
|
}
|
||||||
|
)
|
||||||
response['Location'] = new_cst.get_absolute_url()
|
response['Location'] = new_cst.get_absolute_url()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# TODO: response schema
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=s.CstRenameSerializer,
|
|
||||||
summary='rename constituenta',
|
summary='rename constituenta',
|
||||||
tags=['Constituenta']
|
tags=['Constituenta'],
|
||||||
|
request=s.CstRenameSerializer,
|
||||||
|
responses={c.HTTP_200_OK: s.NewCstResponse}
|
||||||
)
|
)
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-rename')
|
@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.apply_mapping(mapping, change_aliases=False)
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
cst = m.Constituenta.objects.get(pk=serializer.validated_data['id'])
|
cst = m.Constituenta.objects.get(pk=serializer.validated_data['id'])
|
||||||
return Response(status=200, data={
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data={
|
||||||
'new_cst': s.ConstituentaSerializer(cst).data,
|
'new_cst': s.ConstituentaSerializer(cst).data,
|
||||||
'schema': s.RSFormParseSerializer(schema).data
|
'schema': s.RSFormParseSerializer(schema.item).data
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: response schema
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=s.CstListSerializer,
|
|
||||||
summary='delete constituents',
|
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')
|
@action(detail=True, methods=['patch'], url_path='cst-multidelete')
|
||||||
def cst_multidelete(self, request, pk):
|
def cst_multidelete(self, request, pk):
|
||||||
|
@ -238,13 +254,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema.delete_cst(serializer.validated_data['constituents'])
|
schema.delete_cst(serializer.validated_data['constituents'])
|
||||||
schema.item.refresh_from_db()
|
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(
|
@extend_schema(
|
||||||
request=s.CstMoveSerializer,
|
|
||||||
summary='move constituenta',
|
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')
|
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
||||||
def cst_moveto(self, request, pk):
|
def cst_moveto(self, request, pk):
|
||||||
|
@ -254,26 +273,32 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema.move_cst(serializer.validated_data['constituents'], serializer.validated_data['move_to'])
|
schema.move_cst(serializer.validated_data['constituents'], serializer.validated_data['move_to'])
|
||||||
schema.item.refresh_from_db()
|
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(
|
@extend_schema(
|
||||||
request=None,
|
|
||||||
summary='reset aliases, update expressions and references',
|
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')
|
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
||||||
def reset_aliases(self, request, pk):
|
def reset_aliases(self, request, pk):
|
||||||
''' Endpoint: Recreate all aliases based on order. '''
|
''' Endpoint: Recreate all aliases based on order. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_schema()
|
||||||
schema.reset_aliases()
|
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(
|
@extend_schema(
|
||||||
request=s.RSFormUploadSerializer,
|
|
||||||
summary='load data from TRS file',
|
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')
|
@action(detail=True, methods=['patch'], url_path='load-trs')
|
||||||
def load_trs(self, request, pk):
|
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 = s.RSFormTRSSerializer(data=data, context={'load_meta': load_metadata})
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema = serializer.save()
|
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(
|
@extend_schema(
|
||||||
request=None,
|
|
||||||
summary='get all constituents data from DB',
|
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'])
|
@action(detail=True, methods=['get'])
|
||||||
def contents(self, request, pk):
|
def contents(self, request, pk):
|
||||||
''' Endpoint: View schema db contents (including constituents). '''
|
''' Endpoint: View schema db contents (including constituents). '''
|
||||||
schema = s.RSFormSerializer(self._get_schema()).data
|
schema = s.RSFormSerializer(self.get_object())
|
||||||
return Response(schema)
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=schema.data
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: response schema
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=None,
|
|
||||||
summary='get all constituents data and parses',
|
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'])
|
@action(detail=True, methods=['get'])
|
||||||
def details(self, request, pk):
|
def details(self, request, pk):
|
||||||
''' Endpoint: Detailed schema view including statuses and parse. '''
|
''' Endpoint: Detailed schema view including statuses and parse. '''
|
||||||
schema = self._get_schema()
|
schema = self._get_schema()
|
||||||
serializer = s.RSFormParseSerializer(schema)
|
serializer = s.RSFormParseSerializer(schema.item)
|
||||||
return Response(serializer.data)
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=serializer.data
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: response schema
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=s.ExpressionSerializer,
|
|
||||||
summary='check RSLang expression',
|
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'])
|
@action(detail=True, methods=['post'])
|
||||||
def check(self, request, pk):
|
def check(self, request, pk):
|
||||||
|
@ -329,13 +363,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
expression = serializer.validated_data['expression']
|
expression = serializer.validated_data['expression']
|
||||||
schema = s.PyConceptAdapter(self._get_schema())
|
schema = s.PyConceptAdapter(self._get_schema())
|
||||||
result = pyconcept.check_expression(json.dumps(schema.data), expression)
|
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(
|
@extend_schema(
|
||||||
request=s.TextSerializer,
|
|
||||||
responses={200: s.ResolverSerializer},
|
|
||||||
summary='resolve text with references',
|
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'])
|
@action(detail=True, methods=['post'])
|
||||||
def resolve(self, request, pk):
|
def resolve(self, request, pk):
|
||||||
|
@ -345,13 +382,16 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
text = serializer.validated_data['text']
|
text = serializer.validated_data['text']
|
||||||
resolver = self._get_schema().resolver()
|
resolver = self._get_schema().resolver()
|
||||||
resolver.resolve(text)
|
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(
|
@extend_schema(
|
||||||
responses={(200, 'application/zip'): bytes},
|
|
||||||
summary='export as TRS file',
|
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')
|
@action(detail=True, methods=['get'], url_path='export-trs')
|
||||||
def export_trs(self, request, pk):
|
def export_trs(self, request, pk):
|
||||||
|
@ -376,7 +416,9 @@ class TrsImportView(views.APIView):
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='import TRS file into RSForm',
|
summary='import TRS file into RSForm',
|
||||||
tags=['RSForm']
|
tags=['RSForm'],
|
||||||
|
request=s.FileSerializer,
|
||||||
|
responses={c.HTTP_201_CREATED: s.LibraryItemSerializer}
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
data = utils.read_trs(request.FILES['file'].file)
|
data = utils.read_trs(request.FILES['file'].file)
|
||||||
|
@ -388,12 +430,17 @@ class TrsImportView(views.APIView):
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema = serializer.save()
|
schema = serializer.save()
|
||||||
result = s.LibraryItemSerializer(schema.item)
|
result = s.LibraryItemSerializer(schema.item)
|
||||||
return Response(status=201, data=result.data)
|
return Response(
|
||||||
|
status=c.HTTP_201_CREATED,
|
||||||
|
data=result.data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='create new RSForm empty or from file',
|
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'])
|
@api_view(['POST'])
|
||||||
def create_rsform(request):
|
def create_rsform(request):
|
||||||
|
@ -419,7 +466,10 @@ def create_rsform(request):
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
schema = serializer.save()
|
schema = serializer.save()
|
||||||
result = s.LibraryItemSerializer(schema.item)
|
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):
|
def _prepare_rsform_data(data: dict, request, owner: m.User):
|
||||||
data['owner'] = owner
|
data['owner'] = owner
|
||||||
|
@ -443,11 +493,12 @@ def _prepare_rsform_data(data: dict, request, owner: m.User):
|
||||||
data['is_canonical'] = is_canonical
|
data['is_canonical'] = is_canonical
|
||||||
|
|
||||||
|
|
||||||
# TODO: define schema for response
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=s.ExpressionSerializer,
|
|
||||||
summary='RS expression into Syntax Tree',
|
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'])
|
@api_view(['POST'])
|
||||||
def parse_expression(request):
|
def parse_expression(request):
|
||||||
|
@ -456,14 +507,18 @@ def parse_expression(request):
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
expression = serializer.validated_data['expression']
|
expression = serializer.validated_data['expression']
|
||||||
result = pyconcept.parse_expression(expression)
|
result = pyconcept.parse_expression(expression)
|
||||||
return Response(json.loads(result))
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=json.loads(result)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=s.ExpressionSerializer,
|
|
||||||
responses={200: s.ResultTextSerializer},
|
|
||||||
summary='Unicode syntax to ASCII TeX',
|
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'])
|
@api_view(['POST'])
|
||||||
def convert_to_ascii(request):
|
def convert_to_ascii(request):
|
||||||
|
@ -476,10 +531,11 @@ def convert_to_ascii(request):
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=s.ExpressionSerializer,
|
|
||||||
responses={200: s.ResultTextSerializer},
|
|
||||||
summary='ASCII TeX syntax to Unicode symbols',
|
summary='ASCII TeX syntax to Unicode symbols',
|
||||||
tags=['Functions']
|
tags=['FormalLanguage'],
|
||||||
|
request=s.ExpressionSerializer,
|
||||||
|
responses={200: s.ResultTextResponse},
|
||||||
|
auth=None
|
||||||
)
|
)
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def convert_to_math(request):
|
def convert_to_math(request):
|
||||||
|
|
|
@ -7,9 +7,15 @@ from apps.rsform.models import Subscription
|
||||||
from . import models
|
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):
|
class LoginSerializer(serializers.Serializer):
|
||||||
''' Serializer: User authentification by login/password. '''
|
''' Serializer: User authentification by login/password. '''
|
||||||
# TODO: declare schema
|
|
||||||
username = serializers.CharField(
|
username = serializers.CharField(
|
||||||
label='Имя пользователя',
|
label='Имя пользователя',
|
||||||
write_only=True
|
write_only=True
|
||||||
|
@ -44,7 +50,13 @@ class LoginSerializer(serializers.Serializer):
|
||||||
|
|
||||||
class AuthSerializer(serializers.Serializer):
|
class AuthSerializer(serializers.Serializer):
|
||||||
''' Serializer: Authentication data. '''
|
''' 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:
|
def to_representation(self, instance: models.User) -> dict:
|
||||||
if instance.is_anonymous:
|
if instance.is_anonymous:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,40 +1,52 @@
|
||||||
''' REST API: User profile and Authentification. '''
|
''' REST API: User profile and Authentification. '''
|
||||||
from django.contrib.auth import login, logout
|
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 rest_framework.response import Response
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers as s
|
||||||
from . import models
|
from . import models as m
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=['Auth'])
|
|
||||||
@extend_schema_view()
|
|
||||||
class LoginAPIView(views.APIView):
|
class LoginAPIView(views.APIView):
|
||||||
''' Endpoint: Login via username + password. '''
|
''' Endpoint: Login via username + password. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
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):
|
def post(self, request):
|
||||||
serializer = serializers.LoginSerializer(
|
serializer = s.LoginSerializer(
|
||||||
data=self.request.data,
|
data=self.request.data,
|
||||||
context={'request': self.request}
|
context={'request': self.request}
|
||||||
)
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
user = serializer.validated_data['user']
|
user = serializer.validated_data['user']
|
||||||
login(request, 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):
|
class LogoutAPIView(views.APIView):
|
||||||
''' Endpoint: Logout. '''
|
''' Endpoint: Logout. '''
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
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):
|
def post(self, request):
|
||||||
logout(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'])
|
@extend_schema(tags=['User'])
|
||||||
|
@ -42,7 +54,7 @@ class LogoutAPIView(views.APIView):
|
||||||
class SignupAPIView(generics.CreateAPIView):
|
class SignupAPIView(generics.CreateAPIView):
|
||||||
''' Endpoint: Register user. '''
|
''' Endpoint: Register user. '''
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
serializer_class = serializers.SignupSerializer
|
serializer_class = s.SignupSerializer
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=['Auth'])
|
@extend_schema(tags=['Auth'])
|
||||||
|
@ -50,7 +62,7 @@ class SignupAPIView(generics.CreateAPIView):
|
||||||
class AuthAPIView(generics.RetrieveAPIView):
|
class AuthAPIView(generics.RetrieveAPIView):
|
||||||
''' Endpoint: Current user info. '''
|
''' Endpoint: Current user info. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.AuthSerializer
|
serializer_class = s.AuthSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
@ -61,10 +73,10 @@ class AuthAPIView(generics.RetrieveAPIView):
|
||||||
class ActiveUsersView(generics.ListAPIView):
|
class ActiveUsersView(generics.ListAPIView):
|
||||||
''' Endpoint: Get list of active users. '''
|
''' Endpoint: Get list of active users. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = s.UserSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return models.User.objects.filter(is_active=True)
|
return m.User.objects.filter(is_active=True)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=['User'])
|
@extend_schema(tags=['User'])
|
||||||
|
@ -72,14 +84,12 @@ class ActiveUsersView(generics.ListAPIView):
|
||||||
class UserProfileAPIView(generics.RetrieveUpdateAPIView):
|
class UserProfileAPIView(generics.RetrieveUpdateAPIView):
|
||||||
''' Endpoint: User profile. '''
|
''' Endpoint: User profile. '''
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = s.UserSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=['Auth'])
|
|
||||||
@extend_schema_view()
|
|
||||||
class UpdatePassword(views.APIView):
|
class UpdatePassword(views.APIView):
|
||||||
''' Endpoint: Change password for current user. '''
|
''' Endpoint: Change password for current user. '''
|
||||||
permission_classes = (permissions.IsAuthenticated, )
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
@ -87,17 +97,25 @@ class UpdatePassword(views.APIView):
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
return self.request.user
|
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):
|
def patch(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
serializer = serializers.ChangePasswordSerializer(data=request.data)
|
serializer = s.ChangePasswordSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
old_password = serializer.data.get("old_password")
|
old_password = serializer.data.get("old_password")
|
||||||
if not self.object.check_password(old_password):
|
if not self.object.check_password(old_password):
|
||||||
return Response({"old_password": ["Wrong 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
|
# Note: set_password also hashes the password that the user will get
|
||||||
self.object.set_password(serializer.data.get("new_password"))
|
self.object.set_password(serializer.data.get("new_password"))
|
||||||
self.object.save()
|
self.object.save()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=c.HTTP_204_NO_CONTENT)
|
||||||
|
return Response(serializer.errors, status=c.HTTP_400_BAD_REQUEST)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
|
@ -86,9 +86,10 @@ MIDDLEWARE = [
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'project.urls'
|
ROOT_URLCONF = 'project.urls'
|
||||||
LOGIN_URL = '/admin/login/'
|
LOGIN_URL = '/admin/login'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
LOGOUT_REDIRECT_URL = '/'
|
LOGOUT_REDIRECT_URL = '/'
|
||||||
|
APPEND_SLASH = False
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
@ -140,6 +141,20 @@ SPECTACULAR_SETTINGS = {
|
||||||
'DESCRIPTION': 'Портал для работы с экспликациями концептуальных схем',
|
'DESCRIPTION': 'Портал для работы с экспликациями концептуальных схем',
|
||||||
'VERSION': '0.1.0',
|
'VERSION': '0.1.0',
|
||||||
'SERVE_INCLUDE_SCHEMA': False,
|
'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('admin', admin.site.urls),
|
||||||
path('api/', include('apps.rsform.urls')),
|
path('api/', include('apps.rsform.urls')),
|
||||||
path('users/', include('apps.users.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('schema', SpectacularAPIView.as_view(), name='schema'),
|
||||||
path('redoc', SpectacularRedocView.as_view()),
|
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)
|
] + 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 {
|
export interface IConstituentaList {
|
||||||
items: IConstituentaID[]
|
items: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICstCreateData
|
export interface ICstCreateData
|
||||||
|
|
|
@ -64,7 +64,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
||||||
}, -1);
|
}, -1);
|
||||||
const target = Math.max(0, currentIndex - 1) + 1
|
const target = Math.max(0, currentIndex - 1) + 1
|
||||||
const data = {
|
const data = {
|
||||||
items: selected.map(id => ({ id: id })),
|
items: selected,
|
||||||
move_to: target
|
move_to: target
|
||||||
}
|
}
|
||||||
cstMoveTo(data, () => {
|
cstMoveTo(data, () => {
|
||||||
|
@ -95,7 +95,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
||||||
}, -1);
|
}, -1);
|
||||||
const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
||||||
const data: ICstMovetoData = {
|
const data: ICstMovetoData = {
|
||||||
items: selected.map(id => ({ id: id })),
|
items: selected,
|
||||||
move_to: target
|
move_to: target
|
||||||
}
|
}
|
||||||
cstMoveTo(data, () => {
|
cstMoveTo(data, () => {
|
||||||
|
|
|
@ -180,7 +180,7 @@ function RSTabs() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
items: deleted.map(id => ({ id: id }))
|
items: deleted
|
||||||
};
|
};
|
||||||
let activeIndex = schema.items.findIndex(cst => cst.id === activeID);
|
let activeIndex = schema.items.findIndex(cst => cst.id === activeID);
|
||||||
cstDelete(data, () => {
|
cstDelete(data, () => {
|
||||||
|
|
|
@ -220,7 +220,7 @@ export function postNewConstituenta(schema: string, request: FrontExchange<ICstC
|
||||||
|
|
||||||
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
|
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
|
||||||
AxiosPatch({
|
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`,
|
endpoint: `/api/rsforms/${schema}/cst-multidelete`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const urls = {
|
||||||
|
|
||||||
gitrepo: 'https://github.com/IRBorisov/ConceptPortal',
|
gitrepo: 'https://github.com/IRBorisov/ConceptPortal',
|
||||||
mailportal: 'mailto:portal@acconcept.ru',
|
mailportal: 'mailto:portal@acconcept.ru',
|
||||||
restapi: 'https://api.portal.acconcept.ru/docs'
|
restapi: 'https://api.portal.acconcept.ru/docs/'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resources = {
|
export const resources = {
|
||||||
|
|
|
@ -14,8 +14,8 @@ function RunServer() {
|
||||||
BackendRun
|
BackendRun
|
||||||
FrontendRun
|
FrontendRun
|
||||||
Start-Sleep -Seconds 1
|
Start-Sleep -Seconds 1
|
||||||
Start-Process "http://localhost:8000/"
|
Start-Process "http://localhost:8000"
|
||||||
Start-Process "http://localhost:3000/"
|
Start-Process "http://localhost:3000"
|
||||||
}
|
}
|
||||||
|
|
||||||
function BackendRun() {
|
function BackendRun() {
|
||||||
|
|
|
@ -8,4 +8,5 @@ git pull
|
||||||
|
|
||||||
docker compose --file "${COMPOSE_FILE}" up --build --detach
|
docker compose --file "${COMPOSE_FILE}" up --build --detach
|
||||||
docker image prune --all --force
|
docker image prune --all --force
|
||||||
|
sleep 5
|
||||||
docker compose --file "${COMPOSE_FILE}" restart
|
docker compose --file "${COMPOSE_FILE}" restart
|
Loading…
Reference in New Issue
Block a user