From 49efe452b424fb967da0b5ce3bbbbb79b66bff48 Mon Sep 17 00:00:00 2001 From: Alexander Timchenko Date: Fri, 3 Nov 2023 18:57:59 +0300 Subject: [PATCH] =?UTF-8?q?feature:=20=D0=9F=D0=BE=D0=BB=D1=8F=20=D1=88?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=D0=BE=D0=B2=20=D0=B4=D0=BE=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D1=8B=20=D0=B7=D0=BD=D0=B0=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20length=20=D0=B8=20=D1=81=D0=B3?= =?UTF-8?q?=D1=80=D1=83=D0=BF=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=81=D0=BE=D0=B3=D0=BB=D0=B0=D1=81=D0=BD=D0=BE?= =?UTF-8?q?=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/v1/serializers.py | 78 ++++++++++++++++++- backend/backend/settings.py | 17 ++-- ...\320\265\320\275\320\270\321\217_tpl.json" | 72 +++++++++++------ ...\321\202\321\201\320\260\320\264_tpl.json" | 27 ++++--- ...\320\277\321\203\321\201\320\272_tpl.json" | 24 ++++-- ...\320\275\320\267\320\270\321\217_tpl.json" | 54 ++++++++----- backend/documents/admin.py | 11 ++- .../migrations/0024_templatefield_length.py | 18 +++++ backend/documents/models.py | 10 +-- 9 files changed, 234 insertions(+), 77 deletions(-) create mode 100644 backend/documents/migrations/0024_templatefield_length.py diff --git a/backend/api/v1/serializers.py b/backend/api/v1/serializers.py index 81e76c8..b8b34f6 100644 --- a/backend/api/v1/serializers.py +++ b/backend/api/v1/serializers.py @@ -43,9 +43,53 @@ class Meta: "group_name", "type", "mask", + "length", ) +class TemplateFieldSerializerMinified(serializers.ModelSerializer): + """Сериализатор поля шаблона сокращенный (без полей группы)""" + + type = serializers.SlugRelatedField(slug_field="type", read_only=True) + mask = serializers.CharField(source="type.mask", read_only=True) + + class Meta: + model = TemplateField + fields = ( + "id", + "tag", + "name", + "hint", + "type", + "mask", + "length", + ) + + +class TemplateGroupSerializer(serializers.ModelSerializer): + """Сериализатор группы полей шаблона""" + + fields = TemplateFieldSerializerMinified( + read_only=True, + many=True, + # source="fields", + allow_empty=True, + ) + + class Meta: + model = TemplateField + fields = ( + "id", + "name", + "fields", + ) + + def to_representation(self, instance): + response = super().to_representation(instance) + response["fields"].sort(key=lambda x: x["id"]) + return response + + class TemplateSerializerMinified(serializers.ModelSerializer): """Сериализатор шаблонов сокращенный.""" @@ -73,8 +117,8 @@ def get_is_favorited(self, template: Template) -> bool: ).exists() -class TemplateSerializer(TemplateSerializerMinified): - """Сериализатор шаблона.""" +class TemplateSerializerPlain(TemplateSerializerMinified): + """Сериализатор шаблона (без вложенности полей в группы).""" fields = TemplateFieldSerializer( read_only=True, @@ -90,6 +134,36 @@ class Meta(TemplateSerializerMinified.Meta): read_only_fields = ("is_favorited",) +class TemplateSerializer(TemplateSerializerMinified): + """Сериализатор шаблона (поля сгруппированы внутри grouped_fields).""" + + grouped_fields = TemplateGroupSerializer( + read_only=True, + many=True, + source="field_groups", + allow_empty=True, + ) + ungrouped_fields = serializers.SerializerMethodField() + + class Meta(TemplateSerializerMinified.Meta): + model = Template + exclude = ("template",) + read_only_fields = ( + "is_favorited", + "grouped_fields", + "ungrouped_fields", + ) + + def get_ungrouped_fields(self, instance): + solo_fields = instance.fields.filter(group=None).order_by("id") + return TemplateFieldSerializerMinified(solo_fields, many=True).data + + def to_representation(self, instance): + response = super().to_representation(instance) + response["grouped_fields"].sort(key=lambda x: x["id"]) + return response + + class DocumentFieldSerializer(serializers.ModelSerializer): """Сериализатор поля документов.""" diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 20817f9..85d319e 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -23,7 +23,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "drf_yasg", - 'corsheaders', + "corsheaders", "rest_framework", "djoser", "rest_framework.authtoken", @@ -43,8 +43,8 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.common.CommonMiddleware', + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", ] ROOT_URLCONF = "backend.urls" @@ -67,8 +67,7 @@ WSGI_APPLICATION = "backend.wsgi.application" - -if os.getenv('BD') == 'sqlite': +if os.getenv("BD") == "sqlite": DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", @@ -175,10 +174,8 @@ "BASE_PATH": "https://documents-template.site/api/", } -CORS_ALLOW_ALL_ORIGINS = True # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect +CORS_ALLOW_ALL_ORIGINS = True # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect CORS_ALLOWED_ORIGINS = [ - 'http://localhost:3000', -] -CORS_ALLOWED_HOSTS = [ - 'localhost' + "http://localhost:3000", ] +CORS_ALLOWED_HOSTS = ["localhost"] diff --git "a/backend/data/\320\264\320\276\320\263\320\276\320\262\320\276\321\200_\320\275\320\260\320\271\320\274\320\260_\320\266\320\270\320\273\320\276\320\263\320\276_\320\277\320\276\320\274\320\265\321\211\320\265\320\275\320\270\321\217_tpl.json" "b/backend/data/\320\264\320\276\320\263\320\276\320\262\320\276\321\200_\320\275\320\260\320\271\320\274\320\260_\320\266\320\270\320\273\320\276\320\263\320\276_\320\277\320\276\320\274\320\265\321\211\320\265\320\275\320\270\321\217_tpl.json" index b6ccee7..fb87e0e 100644 --- "a/backend/data/\320\264\320\276\320\263\320\276\320\262\320\276\321\200_\320\275\320\260\320\271\320\274\320\260_\320\266\320\270\320\273\320\276\320\263\320\276_\320\277\320\276\320\274\320\265\321\211\320\265\320\275\320\270\321\217_tpl.json" +++ "b/backend/data/\320\264\320\276\320\263\320\276\320\262\320\276\321\200_\320\275\320\260\320\271\320\274\320\260_\320\266\320\270\320\273\320\276\320\263\320\276_\320\277\320\276\320\274\320\265\321\211\320\265\320\275\320\270\321\217_tpl.json" @@ -9,21 +9,24 @@ "name": "Наименование населенного пункта", "hint": "г. Москва", "group": 1, - "type": "str20" + "type": "str20", + "length": 40 }, { "tag": "ДоговорДата", "name": "Дата заключения договора", "hint": "дд.мм.гггг", "group": 1, - "type": "date" + "type": "date", + "length": 40 }, { "tag": "НаймодательФИО", "name": "ФИО собственника", "hint": "Иванов Иван Иванович", "group": 2, - "type": "fio" + "type": "fio", + "length": 40 }, { @@ -31,35 +34,40 @@ "name": "Серия и номер паспорта", "hint": "Только цифры без пробелов", "group": 2, - "type": "d10" + "type": "d10", + "length": 40 }, { "tag": "НаймодательПаспортДата", "name": "Дата выдачи паспорта", "hint": "мм.дд.гггг", "group": 2, - "type": "date" + "type": "date", + "length": 40 }, { "tag": "НаймодательПаспортВыданНаименование", "name": "Наименование выдавшего органа", "hint": "", "group": 2, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "НаймодательПаспортВыданКод", "name": "Код подразделения", "hint": "Только цифры, без пробелов", "group": 2, - "type": "ddd_ddd" + "type": "ddd_ddd", + "length": 40 }, { "tag": "НанимательФИО", "name": "ФИО квартиросъемщика", "hint": "Сидоров Петр Андреевич", "group": 3, - "type": "fio" + "type": "fio", + "length": 40 }, { @@ -67,112 +75,128 @@ "name": "Серия и номер паспорта", "hint": "Только цифры без пробелов", "group": 3, - "type": "d10" + "type": "d10", + "length": 40 }, { "tag": "НанимательПаспортДата", "name": "Дата выдачи паспорта", "hint": "мм.дд.гггг", "group": 3, - "type": "date" + "type": "date", + "length": 40 }, { "tag": "НанимательПаспортВыданНаименование", "name": "Наименование выдавшего органа", "hint": "", "group": 3, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "НанимательПаспортВыданКод", "name": "Код подразделения", "hint": "Только цифры, без пробелов", "group": 3, - "type": "ddd_ddd" + "type": "ddd_ddd", + "length": 40 }, { "tag": "ДоговорАдрес", "name": "Полный адрес квартиры", "hint": "Город, улица, дом, квартира", "group": 4, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "ДоговорДокументы", "name": "Правоустанавливающие документы (укажите наименование документа, даты, серию и номер)", "hint": "Например, Свидетельство о государственной регистрации права от дд.мм.гггг 33 АА 99999 от 02.12.2008", "group": 4, - "type": "str" + "type": "str", + "length": 40 }, { "tag": "ДоговорЖильцы", "name": "ФИО лиц, которые будут проживать в квартире", "hint": "", "group": 4, - "type": "list_fio" + "type": "list_fio", + "length": 40 }, { "tag": "ПраваСрокУведомления", "name": "Количество дней, когда необходимо предупредить владельца о расторжении договора", "hint": "14", "group": 5, - "type": "int" + "type": "int", + "length": 40 }, { "tag": "ПраваСрокВъезда", "name": "Количество дней после заключения договора, когда можно заехать в квартиру", "hint": "14", "group": 5, - "type": "int" + "type": "int", + "length": 40 }, { "tag": "ПраваСрокПродления", "name": "Количество дней до окончания договора, когда можно перезаключить его на тех же условиях", "hint": "30", "group": 5, - "type": "int" + "type": "int", + "length": 40 }, { "tag": "ОплатаСпособ", "name": "Способ оплаты", "hint": "Например, наличными или на лицевой счет", "group": 6, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "ОплатаСумма", "name": "Сумма оплаты в месяц", "hint": "В рублях", "group": 6, - "type": "currency" + "type": "currency", + "length": 40 }, { "tag": "ОплатаАванс", "name": "Количество месяцев, за которые выплачивается аванс при въезде", "hint": "", "group": 6, - "type": "int" + "type": "int", + "length": 40 }, { "tag": "ОплатаДата", "name": "Дата оплаты по договору", "hint": "дд.мм.гггг", "group": 6, - "type": "date" + "type": "date", + "length": 40 }, { "tag": "Срок", "name": "Количество месяцев, на которые заключается договор", "hint": "При заключении договора на более 12 мес. необходима его обязательная регистрация в Росреестре", "group": 7, - "type": "int" + "type": "int", + "length": 40 }, { "tag": "ДатаОкончания", "name": "Дата окончания договора", "hint": "дд.мм.гггг", "group": 7, - "type": "date" + "type": "date", + "length": 40 } ], "groups": [ diff --git "a/backend/data/\320\267\320\260\321\217\320\262\320\273\320\265\320\275\320\270\320\265_\320\264\320\265\321\202\321\201\320\260\320\264_tpl.json" "b/backend/data/\320\267\320\260\321\217\320\262\320\273\320\265\320\275\320\270\320\265_\320\264\320\265\321\202\321\201\320\260\320\264_tpl.json" index 506bb0e..1405985 100644 --- "a/backend/data/\320\267\320\260\321\217\320\262\320\273\320\265\320\275\320\270\320\265_\320\264\320\265\321\202\321\201\320\260\320\264_tpl.json" +++ "b/backend/data/\320\267\320\260\321\217\320\262\320\273\320\265\320\275\320\270\320\265_\320\264\320\265\321\202\321\201\320\260\320\264_tpl.json" @@ -9,14 +9,16 @@ "name": "Номер и название детского сада", "hint": "66 Непоседы", "group": 1, - "type": "str20" + "type": "str20", + "length": 40 }, { "tag": "АдресатФИО", "name": "ФИО заведующего (укажите в дательном падеже)", "hint": "Ивановой Ирине Петровне", "group": 2, - "type": "fio" + "type": "fio", + "length": 40 }, { @@ -24,49 +26,56 @@ "name": "ФИО родителя/законного представителя (в родительном падеже)", "hint": "Иванова Ивана Ивановича", "group": 3, - "type": "fio" + "type": "fio", + "length": 40 }, { "tag": "ОтправительПочтовыйАдрес", "name": "Почтовый адрес", "hint": "город, улица, номер квартиры", "group": 3, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "РебенокФИО", "name": "ФИО ребенка (в творительном падеже)", "hint": "Ивановым Данилой Ивановичем", "group": 4, - "type": "fio" + "type": "fio", + "length": 40 }, { "tag": "РебенокГруппа", "name": "Номер или название группы", "hint": "№3 или средняя", "group": 4, - "type": "str20" + "type": "str20", + "length": 40 }, { "tag": "Дата1", "name": "Дата начала отпуска", "hint": "дд.мм.гггг", "group": null, - "type": "date" + "type": "date", + "length": 40 }, { "tag": "Дата2", "name": "Дата окончания отпуска", "hint": "дд.мм.гггг", "group": null, - "type": "date" + "type": "date", + "length": 40 }, { "tag": "Дата3", "name": "Дата подачи заявления", "hint": "дд.мм.гггг", "group": null, - "type": "date" + "type": "date", + "length": 40 } ], "groups": [ diff --git "a/backend/data/\320\267\320\260\321\217\320\262\320\273\320\265\320\275\320\270\320\265_\320\275\320\260_\320\276\321\202\320\277\321\203\321\201\320\272_tpl.json" "b/backend/data/\320\267\320\260\321\217\320\262\320\273\320\265\320\275\320\270\320\265_\320\275\320\260_\320\276\321\202\320\277\321\203\321\201\320\272_tpl.json" index 81a2a26..48fa371 100644 --- "a/backend/data/\320\267\320\260\321\217\320\262\320\273\320\265\320\275\320\270\320\265_\320\275\320\260_\320\276\321\202\320\277\321\203\321\201\320\272_tpl.json" +++ "b/backend/data/\320\267\320\260\321\217\320\262\320\273\320\265\320\275\320\270\320\265_\320\275\320\260_\320\276\321\202\320\277\321\203\321\201\320\272_tpl.json" @@ -9,56 +9,64 @@ "name": "Должность руководителя", "hint": "В именительном падеже (генеральный директор)", "group": 1, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "РаботодательНаименованиеОрганизации", "name": "Наименование организации", "hint": "ООО АйТиСолюшенс", "group": 1, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "РаботодательФИО", "name": "ФИО руководителя", "hint": "В именительном падеже (Иванов Иван Иванович)", "group": 1, - "type": "fio" + "type": "fio", + "length": 40 }, { "tag": "РаботникДолжность", "name": "Должность", "hint": "в именительном падеже (инженер)", "group": 2, - "type": "str20" + "type": "str20", + "length": 40 }, { "tag": "РаботникФИО", "name": "ФИО заявителя", "hint": "В именительном падеже (Сидорова Анна Андреевна)", "group": 2, - "type": "fio" + "type": "fio", + "length": 40 }, { "tag": "продолжительность", "name": "Продолжительность (дней)", "hint": "14", "group": 3, - "type": "int" + "type": "int", + "length": 40 }, { "tag": "датаНачала", "name": "С какой даты", "hint": "дата в формате дд.мм.гггг (10.02.2023)", "group": 3, - "type": "date" + "type": "date", + "length": 40 }, { "tag": "отпускные", "name": "Сумма отпускных (рублей)", "hint": "-", "group": 3, - "type": "currency" + "type": "currency", + "length": 40 } ], "groups": [ diff --git "a/backend/data/\320\277\321\200\320\265\321\202\320\265\320\275\320\267\320\270\321\217_tpl.json" "b/backend/data/\320\277\321\200\320\265\321\202\320\265\320\275\320\267\320\270\321\217_tpl.json" index 32697f6..cff23ed 100644 --- "a/backend/data/\320\277\321\200\320\265\321\202\320\265\320\275\320\267\320\270\321\217_tpl.json" +++ "b/backend/data/\320\277\321\200\320\265\321\202\320\265\320\275\320\267\320\270\321\217_tpl.json" @@ -9,126 +9,144 @@ "name": "Наименование организации или ФИО исполнителя", "hint": "Организация Лизочки и ее дизайнов", "group": 1, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "АдресатАдрес", "name": "Почтовый адрес", "hint": "город, улица, номер квартиры/офиса", "group": 1, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "ОтправительФИО", "name": "ФИО родительном падеже", "hint": "Иванова Ивана Ивановича", "group": 2, - "type": "fio" + "type": "fio", + "length": 40 }, { "tag": "ОтправительАдрес", "name": "Почтовый адрес заказчика", "hint": "город, улица, номер квартиры/офиса", "group": 2, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "ДоговорДата", "name": "Дата заключения договора", "hint": "дд.мм.гггг", "group": 3, - "type": "date" + "type": "date", + "length": 40 }, { "tag": "ДоговорНомер", "name": "Номер договора", "hint": "Номер договора", "group": 3, - "type": "str20" + "type": "str20", + "length": 40 }, { "tag": "ЗаказчикФИО", "name": "Первая сторона (ФИО/наименование заказчика в творительном падеже)", "hint": "Ивановым Иваном Ивановичем", "group": 3, - "type": "fio" + "type": "fio", + "length": 40 }, { "tag": "ИсполнительФИО", "name": "Вторая сторона (ФИО/наименование исполнителя в творительном падеже)", "hint": "Петровым Олегом Петровичем", "group": 3, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "СуммаДоговора", "name": "Сумма на которую оказаны услуги (в рублях)", "hint": "5250.50", "group": 3, - "type": "currency" + "type": "currency", + "length": 40 }, { "tag": "КвитанцияЧек", "name": "Подтверждающие документы", "hint": "Квитанция, чек об оплате (в творительном падеже) с датой", "group": 3, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "СутьПретензии", "name": "Суть претензии", "hint": "Неисполненные обязательства, например, по возмещению стоимости билетов и т.д.", "group": 4, - "type": "str" + "type": "str", + "length": 40 }, { "tag": "СтатусИсполнения", "name": "Статус исполнения обязательств", "hint": "Например не исполнены в установленный договором срок, исполнены частично и т.д.", "group": 4, - "type": "str40" + "type": "str40", + "length": 40 }, { "tag": "ДоказательстваНеисполнения", "name": "Доказательства неисполнения обязательств", "hint": "Например отсутствие поступления оплаты, справка об отмене рейса т.д.", "group": 4, - "type": "str" + "type": "str", + "length": 40 }, { "tag": "УбыткиСумма", "name": "Сумма материальных убытков (рублей)", "hint": "Укажите в рублях, копейки через запятую", "group": 4, - "type": "currency" + "type": "currency", + "length": 40 }, { "tag": "УбыткиКвитанцияЧек", "name": "Подтверждающие документы", "hint": "Номер квитанции, чек об оплате с указанием даты", "group": 4, - "type": "str" + "type": "str", + "length": 40 }, { "tag": "СрокИсполнения", "name": "Ожидаемый срок исполнения требований", "hint": "Ожидаемый срок исполнения требований", "group": 4, - "type": "int" + "type": "int", + "length": 40 }, { "tag": "СодержаниеТребований", "name": "Содержание предъявляемых требований", "hint": "Содержание предъявляемых требований", "group": 4, - "type": "str" + "type": "str", + "length": 40 }, { "tag": "ДокументыПриложения", "name": "Документы, прикладываемые к претензии", "hint": "Копия договора, причинение убытков и т.д.", "group": 4, - "type": "str" + "type": "str", + "length": 40 } ], "groups": [ diff --git a/backend/documents/admin.py b/backend/documents/admin.py index 73f5826..9ca7c34 100644 --- a/backend/documents/admin.py +++ b/backend/documents/admin.py @@ -75,7 +75,16 @@ class TemplateFieldTypeAdmin(admin.ModelAdmin): @admin.register(models.TemplateField) class TemplateFieldAdmin(admin.ModelAdmin): - list_display = ("id", "template", "tag", "name", "hint", "group", "type") + list_display = ( + "id", + "template", + "tag", + "name", + "hint", + "group", + "type", + "length", + ) list_filter = ("template",) readonly_fields = ("id",) search_fields = ("name",) diff --git a/backend/documents/migrations/0024_templatefield_length.py b/backend/documents/migrations/0024_templatefield_length.py new file mode 100644 index 0000000..4bd25e7 --- /dev/null +++ b/backend/documents/migrations/0024_templatefield_length.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2023-11-03 14:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('documents', '0023_alter_templatefieldtype_mask'), + ] + + operations = [ + migrations.AddField( + model_name='templatefield', + name='length', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Размер поля ввода'), + ), + ] diff --git a/backend/documents/models.py b/backend/documents/models.py index 2acd132..5ebf463 100644 --- a/backend/documents/models.py +++ b/backend/documents/models.py @@ -148,6 +148,9 @@ class TemplateField(models.Model): verbose_name="Тип", help_text="Тип поля", ) + length = models.PositiveIntegerField( + blank=True, null=True, verbose_name="Размер поля ввода" + ) class Meta: verbose_name = "Поле шаблона" @@ -175,9 +178,7 @@ class DocumentField(models.Model): related_name="document_fields", ) value = models.CharField(max_length=255, verbose_name="Содержимое поля") - description = models.TextField( - verbose_name="Описание поля", blank=True - ) + description = models.TextField(verbose_name="Описание поля", blank=True) class Meta: verbose_name = "Поле документа" @@ -209,8 +210,7 @@ class Document(models.Model): auto_now=True, verbose_name="Дата изменения" ) completed = models.BooleanField( - verbose_name="Документ заполнен", - default=False + verbose_name="Документ заполнен", default=False ) description = models.TextField(verbose_name="Описание документа") document_fields = models.ManyToManyField(