new model logic
This commit is contained in:
parent
b22fcd0d4d
commit
93636efecf
|
@ -0,0 +1,431 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import (
|
||||||
|
CustomRequest,
|
||||||
|
FooterModel,
|
||||||
|
LetterTemplate,
|
||||||
|
Page,
|
||||||
|
ReviewModel,
|
||||||
|
Seo,
|
||||||
|
SocialNetwork,
|
||||||
|
Menu,
|
||||||
|
Ref1C,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
@admin.register(Seo)
|
||||||
|
class SeoAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели Seo.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"text",
|
||||||
|
"email",
|
||||||
|
"has_image", # Кастомное поле для проверки наличия изображения
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, по которым можно выполнять поиск
|
||||||
|
search_fields = (
|
||||||
|
"text",
|
||||||
|
"email",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Фильтры для боковой панели
|
||||||
|
list_filter = ("email",)
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
fields = (
|
||||||
|
"text",
|
||||||
|
"email",
|
||||||
|
"blank",
|
||||||
|
"image",
|
||||||
|
)
|
||||||
|
|
||||||
|
@admin.display(description="Есть изображение?", boolean=True)
|
||||||
|
def has_image(self, obj):
|
||||||
|
"""
|
||||||
|
Проверяет, загружено ли изображение для записи.
|
||||||
|
"""
|
||||||
|
return bool(obj.image)
|
||||||
|
|
||||||
|
@admin.register(Page)
|
||||||
|
class PageAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели Page.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"order",
|
||||||
|
"menu_title",
|
||||||
|
"title",
|
||||||
|
"slug_preview", # Кастомное поле для предпросмотра slug
|
||||||
|
"has_image", # Кастомное поле для проверки наличия изображения
|
||||||
|
"external_link", # Внешняя ссылка
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, по которым можно выполнять поиск
|
||||||
|
search_fields = (
|
||||||
|
"menu_title",
|
||||||
|
"title",
|
||||||
|
"slug",
|
||||||
|
"content",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Фильтры для боковой панели
|
||||||
|
list_filter = ("order",)
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
fieldsets = (
|
||||||
|
(
|
||||||
|
"Основная информация",
|
||||||
|
{
|
||||||
|
"fields": ("order", "menu_title", "title", "slug"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Содержимое",
|
||||||
|
{
|
||||||
|
"fields": ("content", "content_rendered"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Медиа и ссылки",
|
||||||
|
{
|
||||||
|
"fields": ("image", "external_link"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, доступные только для чтения
|
||||||
|
readonly_fields = ("content_rendered",)
|
||||||
|
|
||||||
|
@admin.display(description="Slug (предпросмотр)", empty_value="—")
|
||||||
|
def slug_preview(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает краткий предпросмотр slug.
|
||||||
|
"""
|
||||||
|
return obj.slug[:50] + "..." if obj.slug and len(obj.slug) > 50 else obj.slug
|
||||||
|
|
||||||
|
@admin.display(description="Есть изображение?", boolean=True)
|
||||||
|
def has_image(self, obj):
|
||||||
|
"""
|
||||||
|
Проверяет, загружено ли изображение для записи.
|
||||||
|
"""
|
||||||
|
return bool(obj.image)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Menu)
|
||||||
|
class MenuAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели Menu.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"type_display", # Кастомное поле для отображения типа меню
|
||||||
|
"pages_count", # Кастомное поле для подсчета страниц в меню
|
||||||
|
)
|
||||||
|
|
||||||
|
# Фильтры для боковой панели
|
||||||
|
list_filter = ("type",)
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
filter_horizontal = ("pages",) # Удобный виджет для выбора страниц
|
||||||
|
|
||||||
|
# Поля, доступные только для чтения
|
||||||
|
readonly_fields = ()
|
||||||
|
|
||||||
|
@admin.display(description="Тип меню")
|
||||||
|
def type_display(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает читаемое название типа меню.
|
||||||
|
"""
|
||||||
|
return dict(obj._meta.get_field("type").choices).get(obj.type, "Неизвестно")
|
||||||
|
|
||||||
|
@admin.display(description="Количество страниц")
|
||||||
|
def pages_count(self, obj):
|
||||||
|
"""
|
||||||
|
Подсчитывает количество страниц, связанных с меню.
|
||||||
|
"""
|
||||||
|
return obj.pages.count()
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ReviewModel)
|
||||||
|
class ReviewModelAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели ReviewModel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"text_preview", # Кастомное поле для предпросмотра текста отзыва
|
||||||
|
"comment_preview", # Кастомное поле для предпросмотра комментария
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, по которым можно выполнять поиск
|
||||||
|
search_fields = (
|
||||||
|
"text",
|
||||||
|
"comment",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
fields = (
|
||||||
|
"image",
|
||||||
|
"text",
|
||||||
|
"comment",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, доступные только для чтения
|
||||||
|
readonly_fields = ()
|
||||||
|
|
||||||
|
@admin.display(description="Текст (предпросмотр)", empty_value="—")
|
||||||
|
def text_preview(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает краткий предпросмотр текста отзыва.
|
||||||
|
"""
|
||||||
|
return obj.text[:50] + "..." if obj.text and len(obj.text) > 50 else obj.text
|
||||||
|
|
||||||
|
@admin.display(description="Комментарий (предпросмотр)", empty_value="—")
|
||||||
|
def comment_preview(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает краткий предпросмотр комментария.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
obj.comment[:50] + "..."
|
||||||
|
if obj.comment and len(obj.comment) > 50
|
||||||
|
else obj.comment
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Ref1C)
|
||||||
|
class Ref1CAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели Ref1C.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"ref_key",
|
||||||
|
"sync_enabled",
|
||||||
|
"default_value",
|
||||||
|
"last_sync_price",
|
||||||
|
"last_updated",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, по которым можно выполнять поиск
|
||||||
|
search_fields = ("ref_key",)
|
||||||
|
|
||||||
|
# Фильтры для боковой панели
|
||||||
|
list_filter = ("sync_enabled",)
|
||||||
|
|
||||||
|
# Поля, которые можно редактировать прямо в списке
|
||||||
|
list_editable = (
|
||||||
|
"sync_enabled",
|
||||||
|
"default_value",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
fields = (
|
||||||
|
"ref_key",
|
||||||
|
"sync_enabled",
|
||||||
|
"default_value",
|
||||||
|
"last_sync_price",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Добавляем поле "последнее обновление" (только для чтения)
|
||||||
|
readonly_fields = ("last_updated",)
|
||||||
|
|
||||||
|
@admin.display(description="Последнее обновление")
|
||||||
|
def last_updated(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает дату последнего обновления записи.
|
||||||
|
"""
|
||||||
|
return obj.updated_at if hasattr(obj, "updated_at") else "Неизвестно"
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
"""
|
||||||
|
Переопределяем метод сохранения модели для добавления метки времени.
|
||||||
|
"""
|
||||||
|
# Если вы хотите отслеживать время обновления, добавьте поле updated_at в модель
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(FooterModel)
|
||||||
|
class FooterModelAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели FooterModel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"text_preview", # Кастомное поле для предпросмотра текста
|
||||||
|
"small_text", # Флаг малого текста
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, по которым можно выполнять поиск
|
||||||
|
search_fields = ("text",)
|
||||||
|
|
||||||
|
# Фильтры для боковой панели
|
||||||
|
list_filter = ("small_text",)
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
fields = (
|
||||||
|
"text",
|
||||||
|
"small_text",
|
||||||
|
)
|
||||||
|
|
||||||
|
@admin.display(description="Текст (предпросмотр)", empty_value="—")
|
||||||
|
def text_preview(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает краткий предпросмотр текста нижнего колонтитула.
|
||||||
|
"""
|
||||||
|
return obj.text[:50] + "..." if obj.text and len(obj.text) > 50 else obj.text
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CustomRequest)
|
||||||
|
class CustomRequestAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели CustomRequest.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"name", # Имя пользователя
|
||||||
|
"phone", # Телефон пользователя
|
||||||
|
"email", # Email пользователя
|
||||||
|
"date", # Дата создания запроса
|
||||||
|
"fence_info_preview", # Кастомное поле для предпросмотра информации о заборе
|
||||||
|
"privacy", # Согласие на политику конфиденциальности
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, по которым можно выполнять поиск
|
||||||
|
search_fields = (
|
||||||
|
"name",
|
||||||
|
"phone",
|
||||||
|
"email",
|
||||||
|
"fence_info",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Фильтры для боковой панели
|
||||||
|
list_filter = (
|
||||||
|
"privacy",
|
||||||
|
"date",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, доступные только для чтения
|
||||||
|
readonly_fields = ("date",)
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
fieldsets = (
|
||||||
|
(
|
||||||
|
"Основная информация",
|
||||||
|
{
|
||||||
|
"fields": ("name", "phone", "email", "date"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Информация о запросе",
|
||||||
|
{
|
||||||
|
"fields": ("fence_info", "privacy"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@admin.display(description="Информация о заборе (предпросмотр)", empty_value="—")
|
||||||
|
def fence_info_preview(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает краткий предпросмотр информации о заборе.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
obj.fence_info[:50] + "..."
|
||||||
|
if obj.fence_info and len(obj.fence_info) > 50
|
||||||
|
else obj.fence_info
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SocialNetwork)
|
||||||
|
class SocialNetworkAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели SocialNetwork.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"name", # Название социальной сети
|
||||||
|
"link", # Ссылка на социальную сеть
|
||||||
|
"icon_preview", # Кастомное поле для предпросмотра иконки
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, по которым можно выполнять поиск
|
||||||
|
search_fields = (
|
||||||
|
"name",
|
||||||
|
"link",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Фильтры для боковой панели (если нужно)
|
||||||
|
list_filter = ()
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
fields = (
|
||||||
|
"name",
|
||||||
|
"link",
|
||||||
|
"icon",
|
||||||
|
)
|
||||||
|
|
||||||
|
@admin.display(description="Иконка (предпросмотр)", empty_value="—")
|
||||||
|
def icon_preview(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает краткий предпросмотр иконки.
|
||||||
|
"""
|
||||||
|
return obj.icon[:50] + "..." if obj.icon and len(obj.icon) > 50 else obj.icon
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(LetterTemplate)
|
||||||
|
class LetterTemplateAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Класс админки для модели LetterTemplate.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Поля, которые будут отображаться в списке записей
|
||||||
|
list_display = (
|
||||||
|
"subject", # Тема письма
|
||||||
|
"type", # Тип письма
|
||||||
|
"copy_to", # Копия адресата
|
||||||
|
"body_preview", # Кастомное поле для предпросмотра тела письма
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поля, по которым можно выполнять поиск
|
||||||
|
search_fields = (
|
||||||
|
"subject",
|
||||||
|
"body",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Фильтры для боковой панели
|
||||||
|
list_filter = ("type",)
|
||||||
|
|
||||||
|
# Поля, которые будут показаны в форме редактирования
|
||||||
|
fieldsets = (
|
||||||
|
(
|
||||||
|
"Основная информация",
|
||||||
|
{
|
||||||
|
"fields": ("subject", "type", "copy_to"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Тело письма",
|
||||||
|
{
|
||||||
|
"fields": ("body",),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@admin.display(description="Текст письма (предпросмотр)", empty_value="—")
|
||||||
|
def body_preview(self, obj):
|
||||||
|
"""
|
||||||
|
Отображает краткий предпросмотр тела письма.
|
||||||
|
"""
|
||||||
|
return obj.body[:50] + "..." if obj.body and len(obj.body) > 50 else obj.body
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalDataConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'global_data'
|
|
@ -0,0 +1,402 @@
|
||||||
|
from slugify import slugify
|
||||||
|
from django.db import models
|
||||||
|
from django.core.validators import MaxValueValidator
|
||||||
|
|
||||||
|
from markdownfield.models import MarkdownField, RenderedMarkdownField
|
||||||
|
from markdownfield.validators import VALIDATOR_STANDARD
|
||||||
|
|
||||||
|
from mns.utils import group_based_upload_to
|
||||||
|
|
||||||
|
class Seo(models.Model):
|
||||||
|
"""
|
||||||
|
Модель SEO содержит информацию о тексте, электронной почте, дополнительном тексте и изображении.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Первичный ключ, автоинкрементируемый
|
||||||
|
id = models.AutoField(primary_key=True, verbose_name="ID")
|
||||||
|
|
||||||
|
# Поле для хранения текста с максимальной длиной 200 символов
|
||||||
|
text = models.CharField(
|
||||||
|
max_length=200, help_text="Введите текст", verbose_name="Текст"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Поле для хранения email-адреса
|
||||||
|
email = models.EmailField(help_text="Введите Email", verbose_name="Email")
|
||||||
|
|
||||||
|
# Поле для хранения дополнительного текста, может быть пустым
|
||||||
|
blank = models.TextField(blank=True, null=True, verbose_name="Дополнительный текст")
|
||||||
|
|
||||||
|
# Поле для хранения изображения, загружается по указанному пути
|
||||||
|
image = models.ImageField(
|
||||||
|
upload_to=group_based_upload_to,
|
||||||
|
default="", # Пустая строка в качестве значения по умолчанию
|
||||||
|
verbose_name="Изображение",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.text}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "SEO запись"
|
||||||
|
verbose_name_plural = "SEO записи"
|
||||||
|
|
||||||
|
|
||||||
|
class Page(models.Model):
|
||||||
|
"""
|
||||||
|
Модель Page содержит информацию о странице, включая порядок отображения,
|
||||||
|
заголовки меню и страницы, содержимое, слаг, изображение и внешнюю ссылку.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Порядок отображения страницы
|
||||||
|
order = models.IntegerField(
|
||||||
|
verbose_name="Порядок",
|
||||||
|
help_text="Введите порядковый номер страницы для сортировки",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Заголовок меню
|
||||||
|
menu_title = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
verbose_name="Заголовок меню",
|
||||||
|
help_text="Введите заголовок, который будет отображаться в меню",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Заголовок страницы
|
||||||
|
title = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
verbose_name="Заголовок страницы",
|
||||||
|
help_text="Введите заголовок страницы",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Содержимое страницы в формате Markdown
|
||||||
|
content = MarkdownField(
|
||||||
|
rendered_field="content_rendered",
|
||||||
|
validator=VALIDATOR_STANDARD,
|
||||||
|
verbose_name="Содержимое (Markdown)",
|
||||||
|
help_text="Введите содержимое страницы в формате Markdown",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Отрендеренное содержимое страницы
|
||||||
|
content_rendered = RenderedMarkdownField(
|
||||||
|
verbose_name="Отрендеренное содержимое",
|
||||||
|
help_text="Это поле автоматически генерируется из поля 'Содержимое'",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Slug для URL страницы
|
||||||
|
slug = models.SlugField(
|
||||||
|
unique=True,
|
||||||
|
verbose_name="Slug",
|
||||||
|
help_text="Введите уникальный slug для URL страницы",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Изображение страницы
|
||||||
|
image = models.ImageField(
|
||||||
|
upload_to=group_based_upload_to,
|
||||||
|
default=None, # Убедитесь, что это значение корректно
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Изображение",
|
||||||
|
help_text="Выберите изображение для страницы",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Внешняя ссылка
|
||||||
|
external_link = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Внешняя ссылка",
|
||||||
|
help_text="Введите внешнюю ссылку, если она есть",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.menu_title} #{self.slug} {self.order}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Страница"
|
||||||
|
verbose_name_plural = "Страницы"
|
||||||
|
|
||||||
|
|
||||||
|
class Menu(models.Model):
|
||||||
|
"""
|
||||||
|
Модель Menu содержит информацию о меню, включая страницы и тип меню.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Связь с моделью Page (многие ко многим)
|
||||||
|
pages = models.ManyToManyField(
|
||||||
|
Page,
|
||||||
|
verbose_name="Страницы",
|
||||||
|
help_text="Выберите страницы, которые будут включены в меню",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Тип меню (выбор из списка)
|
||||||
|
type = models.IntegerField(
|
||||||
|
choices=[
|
||||||
|
(1, "TopMenu"),
|
||||||
|
],
|
||||||
|
unique=True,
|
||||||
|
verbose_name="Тип меню",
|
||||||
|
help_text="Выберите тип меню",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Возвращает строковое представление объекта модели.
|
||||||
|
"""
|
||||||
|
menu_type = dict(self._meta.get_field("type").choices).get(
|
||||||
|
self.type, "Неизвестно"
|
||||||
|
)
|
||||||
|
return f"Меню {menu_type}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Меню"
|
||||||
|
verbose_name_plural = "Меню"
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewModel(models.Model):
|
||||||
|
"""
|
||||||
|
Модель ReviewModel содержит информацию о отзывах, включая изображение, текст и комментарий.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Изображение отзыва
|
||||||
|
image = models.ImageField(
|
||||||
|
upload_to=group_based_upload_to,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Изображение",
|
||||||
|
help_text="Выберите изображение для отзыва",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Текст отзыва
|
||||||
|
text = models.CharField(
|
||||||
|
max_length=200, # Установите максимальную длину строки
|
||||||
|
verbose_name="Текст",
|
||||||
|
help_text="Введите текст отзыва",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Комментарий к отзыву
|
||||||
|
comment = models.TextField(
|
||||||
|
verbose_name="Комментарий", help_text="Введите комментарий к отзыву"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Возвращает строковое представление объекта модели.
|
||||||
|
"""
|
||||||
|
return f"{self.text}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Отзыв"
|
||||||
|
verbose_name_plural = "Отзывы"
|
||||||
|
|
||||||
|
|
||||||
|
class Ref1C(models.Model):
|
||||||
|
"""
|
||||||
|
Модель для хранения ссылки на данные из 1С (Ref_Key), флага для включения синхронизации,
|
||||||
|
последней синхронизированной цены и значения по умолчанию.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ref_key = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name="Ref_Key",
|
||||||
|
help_text="Уникальный идентификатор из 1С",
|
||||||
|
)
|
||||||
|
sync_enabled = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name="Включить синхронизацию",
|
||||||
|
help_text="Флаг для включения синхронизации данных из 1С",
|
||||||
|
)
|
||||||
|
default_value = models.DecimalField(
|
||||||
|
max_digits=7,
|
||||||
|
decimal_places=2,
|
||||||
|
verbose_name="Значение по умолчанию",
|
||||||
|
help_text="Значение, используемое по умолчанию или при отсутствии данных из 1С",
|
||||||
|
)
|
||||||
|
last_sync_price = models.DecimalField(
|
||||||
|
max_digits=7,
|
||||||
|
decimal_places=2,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Последняя синхронизированная цена",
|
||||||
|
help_text="Последняя цена, полученная из 1С",
|
||||||
|
)
|
||||||
|
updated_at = models.DateTimeField(
|
||||||
|
auto_now=True,
|
||||||
|
verbose_name="Последнее обновление",
|
||||||
|
help_text="Дата и время последнего обновления записи"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Ref_Key: {self.ref_key}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Ссылка на 1С"
|
||||||
|
verbose_name_plural = "Ссылки на 1С"
|
||||||
|
|
||||||
|
|
||||||
|
class FooterModel(models.Model):
|
||||||
|
"""
|
||||||
|
Модель FooterModel содержит информацию о нижнем колонтитуле, включая текст и флаг малого текста.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Текст нижнего колонтитула
|
||||||
|
text = models.TextField(
|
||||||
|
max_length=1000,
|
||||||
|
verbose_name="Текст",
|
||||||
|
help_text="Введите текст нижнего колонтитула",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Флаг малого текста
|
||||||
|
small_text = models.BooleanField(
|
||||||
|
verbose_name="Малый текст",
|
||||||
|
help_text="Отметьте, если текст должен быть маленьким",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Возвращает строковое представление объекта модели.
|
||||||
|
"""
|
||||||
|
return f"{self.text}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Нижний колонтитул"
|
||||||
|
verbose_name_plural = "Нижние колонтитулы"
|
||||||
|
|
||||||
|
|
||||||
|
class CustomRequest(models.Model):
|
||||||
|
"""
|
||||||
|
Модель CustomRequest содержит информацию о пользовательских запросах,
|
||||||
|
включая имя, телефон, email, дату создания, информацию о заборе и согласие на политику конфиденциальности.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Имя пользователя
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100, verbose_name="Имя", help_text="Введите имя пользователя"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Телефон пользователя (необязательное поле)
|
||||||
|
phone = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Телефон",
|
||||||
|
help_text="Введите телефон пользователя",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Email пользователя (необязательное поле)
|
||||||
|
email = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Email",
|
||||||
|
help_text="Введите email пользователя",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Дата и время создания запроса
|
||||||
|
date = models.DateTimeField(
|
||||||
|
auto_now_add=True,
|
||||||
|
verbose_name="Дата создания",
|
||||||
|
help_text="Дата и время создания запроса",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Информация о заборе
|
||||||
|
fence_info = models.TextField(
|
||||||
|
verbose_name="Информация о заборе", help_text="Введите информацию о заборе"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Согласие на политику конфиденциальности
|
||||||
|
privacy = models.BooleanField(
|
||||||
|
verbose_name="Политика конфиденциальности",
|
||||||
|
help_text="Отметьте, если пользователь согласен с политикой конфиденциальности",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Возвращает строковое представление объекта модели.
|
||||||
|
"""
|
||||||
|
return f"{self.name} {self.phone} {self.date}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Пользовательский запрос"
|
||||||
|
verbose_name_plural = "Пользовательские запросы"
|
||||||
|
|
||||||
|
|
||||||
|
class SocialNetwork(models.Model):
|
||||||
|
"""
|
||||||
|
Модель SocialNetwork содержит информацию о социальных сетях, включая название, ссылку и иконку.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Название социальной сети
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
verbose_name="Название",
|
||||||
|
help_text="Введите название социальной сети",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ссылка на социальную сеть
|
||||||
|
link = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
verbose_name="Ссылка",
|
||||||
|
help_text="Введите ссылку на социальную сеть",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Иконка социальной сети (необязательное поле)
|
||||||
|
icon = models.TextField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Иконка",
|
||||||
|
help_text="Введите HTML-код или путь к иконке социальной сети",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Возвращает строковое представление объекта модели.
|
||||||
|
"""
|
||||||
|
return f"{self.name}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Социальная сеть"
|
||||||
|
verbose_name_plural = "Социальные сети"
|
||||||
|
|
||||||
|
|
||||||
|
class LetterTemplate(models.Model):
|
||||||
|
"""
|
||||||
|
Модель LetterTemplate содержит информацию о шаблонах писем, включая тему, тело письма, копию адресата и тип письма.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Тема письма
|
||||||
|
subject = models.CharField(
|
||||||
|
max_length=100, verbose_name="Тема письма", help_text="Введите тему письма"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Тело письма
|
||||||
|
body = models.TextField(
|
||||||
|
verbose_name="Текст письма", help_text="Введите текст письма"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Копия адресата (необязательное поле)
|
||||||
|
copy_to = models.EmailField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Копия адресата",
|
||||||
|
help_text="Введите email для копии письма",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Тип письма (выбор из списка)
|
||||||
|
type = models.CharField(
|
||||||
|
choices=[
|
||||||
|
("user_submit", "Submit"),
|
||||||
|
],
|
||||||
|
unique=True,
|
||||||
|
verbose_name="Тип письма",
|
||||||
|
help_text="Выберите тип письма",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Возвращает строковое представление объекта модели.
|
||||||
|
"""
|
||||||
|
return f"{self.subject}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Шаблон письма"
|
||||||
|
verbose_name_plural = "Шаблоны писем"
|
|
@ -0,0 +1,68 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from mns.logger import logger
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
CustomRequest,
|
||||||
|
FooterModel,
|
||||||
|
LetterTemplate,
|
||||||
|
Page,
|
||||||
|
ReviewModel,
|
||||||
|
Seo,
|
||||||
|
SocialNetwork,
|
||||||
|
Menu,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SeoSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Seo
|
||||||
|
fields = ["id", "text", "email", "blank", "image"]
|
||||||
|
|
||||||
|
|
||||||
|
class PageSerializer(serializers.ModelSerializer):
|
||||||
|
image = serializers.ImageField(use_url=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Page
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class MenuSerializer(serializers.ModelSerializer):
|
||||||
|
pages = PageSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Menu
|
||||||
|
fields = "__all__"
|
||||||
|
depth = 2
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewSerializer(serializers.ModelSerializer):
|
||||||
|
image = serializers.ImageField(use_url=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ReviewModel
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class FooterSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = FooterModel
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class CustomRequestSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CustomRequest
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SocialNetworkSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SocialNetwork
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class LetterTemplateSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = LetterTemplate
|
||||||
|
fields = "__all__"
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,144 @@
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from rest_framework import viewsets, filters
|
||||||
|
|
||||||
|
from mns.logger import logger
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
CustomRequest,
|
||||||
|
FooterModel,
|
||||||
|
LetterTemplate,
|
||||||
|
Page,
|
||||||
|
ReviewModel,
|
||||||
|
Seo,
|
||||||
|
SocialNetwork,
|
||||||
|
Menu,
|
||||||
|
)
|
||||||
|
from .serializers import (
|
||||||
|
CustomRequestSerializer,
|
||||||
|
FooterSerializer,
|
||||||
|
LetterTemplateSerializer,
|
||||||
|
PageSerializer,
|
||||||
|
ReviewSerializer,
|
||||||
|
SeoSerializer,
|
||||||
|
SocialNetworkSerializer,
|
||||||
|
MenuSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
class SeoViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint that allows partners to be viewed or edited.
|
||||||
|
"""
|
||||||
|
|
||||||
|
http_method_names = ["get"]
|
||||||
|
queryset = Seo.objects.all()
|
||||||
|
serializer_class = SeoSerializer
|
||||||
|
|
||||||
|
class PageViewSet(viewsets.ModelViewSet):
|
||||||
|
http_method_names = ["get"]
|
||||||
|
queryset = Page.objects.all()
|
||||||
|
serializer_class = PageSerializer
|
||||||
|
filter_backends = [filters.OrderingFilter]
|
||||||
|
lookup_field = 'slug'
|
||||||
|
|
||||||
|
|
||||||
|
class MenuViewSet(viewsets.ModelViewSet):
|
||||||
|
http_method_names = ["get"]
|
||||||
|
queryset = Menu.objects.all()
|
||||||
|
serializer_class = MenuSerializer
|
||||||
|
filter_backends = [filters.OrderingFilter]
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewViewSet(viewsets.ModelViewSet):
|
||||||
|
http_method_names = ["get"]
|
||||||
|
queryset = ReviewModel.objects.all()
|
||||||
|
serializer_class = ReviewSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class FooterViewSet(viewsets.ModelViewSet):
|
||||||
|
http_method_names = ["get"]
|
||||||
|
queryset = FooterModel.objects.all()
|
||||||
|
serializer_class = FooterSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class CustomRequestViewSet(viewsets.ModelViewSet):
|
||||||
|
http_method_names = ["options", "post"]
|
||||||
|
authentication_classes = ()
|
||||||
|
queryset = CustomRequest.objects.all()
|
||||||
|
serializer_class = CustomRequestSerializer
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
serializer.save()
|
||||||
|
name = self.request.data.get("name")
|
||||||
|
phone = self.request.data.get("phone")
|
||||||
|
email = self.request.data.get("email")
|
||||||
|
fence_info = self.request.data.get("fence_info")
|
||||||
|
total = re.findall(r"Итого.*?(\d+\s?\d+,?\d+).*?₽", fence_info)
|
||||||
|
total_price = 0 if not len(total) else re.sub(r"\s", "", "" or total[0])
|
||||||
|
data = {
|
||||||
|
"fields[TITLE]": f"КУПИЗАБОР {name} {email} {phone}",
|
||||||
|
"fields[NAME]": name,
|
||||||
|
"fields[STATUS_ID]": "NEW",
|
||||||
|
"fields[OPENED]": "Y",
|
||||||
|
# "fields[ASSIGNED_BY_ID]": "34",
|
||||||
|
"fields[PHONE][0][VALUE]": phone,
|
||||||
|
"fields[PHONE][0][VALUE_TYPE]": "WORK",
|
||||||
|
"fields[EMAIL][0][VALUE]": email,
|
||||||
|
"fields[EMAIL][0][VALUE_TYPE]": "WORK",
|
||||||
|
"fields[SOURCE_ID]": "1",
|
||||||
|
"fields[OPPORTUNITY]": total_price,
|
||||||
|
"fields[UF_CRM_1713510698]": fence_info,
|
||||||
|
"fields[UF_CRM_1712154805]": 47,
|
||||||
|
"fields[UF_CRM_1712154416]": datetime.now() + timedelta(days=1),
|
||||||
|
}
|
||||||
|
bx_result = requests.post(
|
||||||
|
f"{settings.BX_WEBHOOK}/crm.lead.add.json",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
bx_result_json = bx_result.json()
|
||||||
|
if bx_result_json["result"]:
|
||||||
|
logger.info(f'Был создан лид {bx_result_json["result"]}')
|
||||||
|
letter_template = LetterTemplate.objects.get(type="user_submit")
|
||||||
|
if email:
|
||||||
|
todo_data = {
|
||||||
|
"fields[OWNER_TYPE_ID]": 1,
|
||||||
|
"fields[OWNER_ID]": bx_result_json["result"],
|
||||||
|
"fields[TYPE_ID]": 4,
|
||||||
|
"fields[SUBJECT]": (
|
||||||
|
"КУПИЗАБОР" if not letter_template else letter_template.subject
|
||||||
|
),
|
||||||
|
"fields[DESCRIPTION]": (
|
||||||
|
f"Данные расчета\n{fence_info}"
|
||||||
|
if not letter_template
|
||||||
|
else re.sub("\[fence\]", fence_info, letter_template.body)
|
||||||
|
),
|
||||||
|
"fields[DESCRIPTION_TYPE]": 2,
|
||||||
|
"fields[COMMUNICATIONS][0][VALUE]": email,
|
||||||
|
"fields[COMPLETED]": "Y",
|
||||||
|
"fields[DIRECTION]": 2,
|
||||||
|
"fields[SETTINGS][MESSAGE_FROM]": "СВС технологии <office@svs-tech.pro>",
|
||||||
|
}
|
||||||
|
bx_result_email = requests.post(
|
||||||
|
f"{settings.BX_WEBHOOK}/crm.activity.add",
|
||||||
|
todo_data,
|
||||||
|
)
|
||||||
|
email_result = bx_result_email.json()
|
||||||
|
if email_result:
|
||||||
|
logger.info(f"Было отправлено письмо {email_result['result']}")
|
||||||
|
|
||||||
|
|
||||||
|
class SocialNetworkViewSet(viewsets.ModelViewSet):
|
||||||
|
http_method_names = ["get"]
|
||||||
|
queryset = SocialNetwork.objects.all()
|
||||||
|
serializer_class = SocialNetworkSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class LetterTemplatesViewSet(viewsets.ModelViewSet):
|
||||||
|
http_method_names = ["get"]
|
||||||
|
queryset = LetterTemplate.objects.all()
|
||||||
|
serializer_class = LetterTemplateSerializer
|
|
@ -1,61 +1,10 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import (
|
from .models import (
|
||||||
AdvantageModel,
|
|
||||||
CalcModel,
|
CalcModel,
|
||||||
CustomRequest,
|
|
||||||
DiscountModel,
|
DiscountModel,
|
||||||
FooterModel,
|
|
||||||
KupiZabor,
|
KupiZabor,
|
||||||
LetterTemplate,
|
|
||||||
Page,
|
|
||||||
ReviewModel,
|
|
||||||
Seo,
|
|
||||||
SocialNetwork,
|
|
||||||
Menu,
|
|
||||||
Ref1C,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
@admin.register(Seo)
|
|
||||||
class SeoAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели Seo.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"id",
|
|
||||||
"text",
|
|
||||||
"email",
|
|
||||||
"has_image", # Кастомное поле для проверки наличия изображения
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = (
|
|
||||||
"text",
|
|
||||||
"email",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели
|
|
||||||
list_filter = ("email",)
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fields = (
|
|
||||||
"text",
|
|
||||||
"email",
|
|
||||||
"blank",
|
|
||||||
"image",
|
|
||||||
)
|
|
||||||
|
|
||||||
@admin.display(description="Есть изображение?", boolean=True)
|
|
||||||
def has_image(self, obj):
|
|
||||||
"""
|
|
||||||
Проверяет, загружено ли изображение для записи.
|
|
||||||
"""
|
|
||||||
return bool(obj.image)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(KupiZabor)
|
@admin.register(KupiZabor)
|
||||||
class KupiZaborAdmin(admin.ModelAdmin):
|
class KupiZaborAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
|
@ -113,156 +62,6 @@ class KupiZaborAdmin(admin.ModelAdmin):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Page)
|
|
||||||
class PageAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели PageModel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"order",
|
|
||||||
"menu_title",
|
|
||||||
"title",
|
|
||||||
"slug_preview", # Кастомное поле для предпросмотра slug
|
|
||||||
"has_image", # Кастомное поле для проверки наличия изображения
|
|
||||||
"external_link", # Внешняя ссылка
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = (
|
|
||||||
"menu_title",
|
|
||||||
"title",
|
|
||||||
"slug",
|
|
||||||
"content",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели
|
|
||||||
list_filter = ("order",)
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fieldsets = (
|
|
||||||
(
|
|
||||||
"Основная информация",
|
|
||||||
{
|
|
||||||
"fields": ("order", "menu_title", "title", "slug"),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Содержимое",
|
|
||||||
{
|
|
||||||
"fields": ("content", "content_rendered"),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Медиа и ссылки",
|
|
||||||
{
|
|
||||||
"fields": ("image", "external_link"),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, доступные только для чтения
|
|
||||||
readonly_fields = ("content_rendered",)
|
|
||||||
|
|
||||||
@admin.display(description="Slug (предпросмотр)", empty_value="—")
|
|
||||||
def slug_preview(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает краткий предпросмотр slug.
|
|
||||||
"""
|
|
||||||
return obj.slug[:50] + "..." if obj.slug and len(obj.slug) > 50 else obj.slug
|
|
||||||
|
|
||||||
@admin.display(description="Есть изображение?", boolean=True)
|
|
||||||
def has_image(self, obj):
|
|
||||||
"""
|
|
||||||
Проверяет, загружено ли изображение для записи.
|
|
||||||
"""
|
|
||||||
return bool(obj.image)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Menu)
|
|
||||||
class MenuAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели Menu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"type_display", # Кастомное поле для отображения типа меню
|
|
||||||
"pages_count", # Кастомное поле для подсчета страниц в меню
|
|
||||||
)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели
|
|
||||||
list_filter = ("type",)
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
filter_horizontal = ("pages",) # Удобный виджет для выбора страниц
|
|
||||||
|
|
||||||
# Поля, доступные только для чтения
|
|
||||||
readonly_fields = ()
|
|
||||||
|
|
||||||
@admin.display(description="Тип меню")
|
|
||||||
def type_display(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает читаемое название типа меню.
|
|
||||||
"""
|
|
||||||
return dict(obj._meta.get_field("type").choices).get(obj.type, "Неизвестно")
|
|
||||||
|
|
||||||
@admin.display(description="Количество страниц")
|
|
||||||
def pages_count(self, obj):
|
|
||||||
"""
|
|
||||||
Подсчитывает количество страниц, связанных с меню.
|
|
||||||
"""
|
|
||||||
return obj.pages.count()
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ReviewModel)
|
|
||||||
class ReviewModelAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели ReviewModel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"text_preview", # Кастомное поле для предпросмотра текста отзыва
|
|
||||||
"comment_preview", # Кастомное поле для предпросмотра комментария
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = (
|
|
||||||
"text",
|
|
||||||
"comment",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fields = (
|
|
||||||
"image",
|
|
||||||
"text",
|
|
||||||
"comment",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, доступные только для чтения
|
|
||||||
readonly_fields = ()
|
|
||||||
|
|
||||||
@admin.display(description="Текст (предпросмотр)", empty_value="—")
|
|
||||||
def text_preview(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает краткий предпросмотр текста отзыва.
|
|
||||||
"""
|
|
||||||
return obj.text[:50] + "..." if obj.text and len(obj.text) > 50 else obj.text
|
|
||||||
|
|
||||||
@admin.display(description="Комментарий (предпросмотр)", empty_value="—")
|
|
||||||
def comment_preview(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает краткий предпросмотр комментария.
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
obj.comment[:50] + "..."
|
|
||||||
if obj.comment and len(obj.comment) > 50
|
|
||||||
else obj.comment
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DiscountModel)
|
@admin.register(DiscountModel)
|
||||||
class DiscountModelAdmin(admin.ModelAdmin):
|
class DiscountModelAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
|
@ -303,59 +102,6 @@ class DiscountModelAdmin(admin.ModelAdmin):
|
||||||
return f"От {obj.min_quantity}"
|
return f"От {obj.min_quantity}"
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Ref1C)
|
|
||||||
class Ref1CAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели Ref1C.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"ref_key",
|
|
||||||
"sync_enabled",
|
|
||||||
"default_value",
|
|
||||||
"last_sync_price",
|
|
||||||
"last_updated",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = ("ref_key",)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели
|
|
||||||
list_filter = ("sync_enabled",)
|
|
||||||
|
|
||||||
# Поля, которые можно редактировать прямо в списке
|
|
||||||
list_editable = (
|
|
||||||
"sync_enabled",
|
|
||||||
"default_value",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fields = (
|
|
||||||
"ref_key",
|
|
||||||
"sync_enabled",
|
|
||||||
"default_value",
|
|
||||||
"last_sync_price",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Добавляем поле "последнее обновление" (только для чтения)
|
|
||||||
readonly_fields = ("last_updated",)
|
|
||||||
|
|
||||||
@admin.display(description="Последнее обновление")
|
|
||||||
def last_updated(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает дату последнего обновления записи.
|
|
||||||
"""
|
|
||||||
return obj.updated_at if hasattr(obj, "updated_at") else "Неизвестно"
|
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
|
||||||
"""
|
|
||||||
Переопределяем метод сохранения модели для добавления метки времени.
|
|
||||||
"""
|
|
||||||
# Если вы хотите отслеживать время обновления, добавьте поле updated_at в модель
|
|
||||||
obj.save()
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CalcModel)
|
@admin.register(CalcModel)
|
||||||
class CalcModelAdmin(admin.ModelAdmin):
|
class CalcModelAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
|
@ -440,219 +186,3 @@ class CalcModelAdmin(admin.ModelAdmin):
|
||||||
", ".join([str(discount) for discount in obj.discount.all()])
|
", ".join([str(discount) for discount in obj.discount.all()])
|
||||||
or "Нет скидок"
|
or "Нет скидок"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdvantageModel)
|
|
||||||
class AdvantageModelAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели AdvantageModel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"title", # Заголовок
|
|
||||||
"content_preview", # Кастомное поле для предпросмотра содержимого
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = (
|
|
||||||
"title",
|
|
||||||
"content",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели (если нужно)
|
|
||||||
list_filter = ()
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fields = (
|
|
||||||
"title",
|
|
||||||
"content",
|
|
||||||
)
|
|
||||||
|
|
||||||
@admin.display(description="Содержимое (предпросмотр)", empty_value="—")
|
|
||||||
def content_preview(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает краткий предпросмотр содержимого.
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
obj.content[:50] + "..."
|
|
||||||
if obj.content and len(obj.content) > 50
|
|
||||||
else obj.content
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(FooterModel)
|
|
||||||
class FooterModelAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели FooterModel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"text_preview", # Кастомное поле для предпросмотра текста
|
|
||||||
"small_text", # Флаг малого текста
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = ("text",)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели
|
|
||||||
list_filter = ("small_text",)
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fields = (
|
|
||||||
"text",
|
|
||||||
"small_text",
|
|
||||||
)
|
|
||||||
|
|
||||||
@admin.display(description="Текст (предпросмотр)", empty_value="—")
|
|
||||||
def text_preview(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает краткий предпросмотр текста нижнего колонтитула.
|
|
||||||
"""
|
|
||||||
return obj.text[:50] + "..." if obj.text and len(obj.text) > 50 else obj.text
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CustomRequest)
|
|
||||||
class CustomRequestAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели CustomRequest.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"name", # Имя пользователя
|
|
||||||
"phone", # Телефон пользователя
|
|
||||||
"email", # Email пользователя
|
|
||||||
"date", # Дата создания запроса
|
|
||||||
"fence_info_preview", # Кастомное поле для предпросмотра информации о заборе
|
|
||||||
"privacy", # Согласие на политику конфиденциальности
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = (
|
|
||||||
"name",
|
|
||||||
"phone",
|
|
||||||
"email",
|
|
||||||
"fence_info",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели
|
|
||||||
list_filter = (
|
|
||||||
"privacy",
|
|
||||||
"date",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, доступные только для чтения
|
|
||||||
readonly_fields = ("date",)
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fieldsets = (
|
|
||||||
(
|
|
||||||
"Основная информация",
|
|
||||||
{
|
|
||||||
"fields": ("name", "phone", "email", "date"),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Информация о запросе",
|
|
||||||
{
|
|
||||||
"fields": ("fence_info", "privacy"),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@admin.display(description="Информация о заборе (предпросмотр)", empty_value="—")
|
|
||||||
def fence_info_preview(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает краткий предпросмотр информации о заборе.
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
obj.fence_info[:50] + "..."
|
|
||||||
if obj.fence_info and len(obj.fence_info) > 50
|
|
||||||
else obj.fence_info
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SocialNetwork)
|
|
||||||
class SocialNetworkAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели SocialNetwork.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"name", # Название социальной сети
|
|
||||||
"link", # Ссылка на социальную сеть
|
|
||||||
"icon_preview", # Кастомное поле для предпросмотра иконки
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = (
|
|
||||||
"name",
|
|
||||||
"link",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели (если нужно)
|
|
||||||
list_filter = ()
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fields = (
|
|
||||||
"name",
|
|
||||||
"link",
|
|
||||||
"icon",
|
|
||||||
)
|
|
||||||
|
|
||||||
@admin.display(description="Иконка (предпросмотр)", empty_value="—")
|
|
||||||
def icon_preview(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает краткий предпросмотр иконки.
|
|
||||||
"""
|
|
||||||
return obj.icon[:50] + "..." if obj.icon and len(obj.icon) > 50 else obj.icon
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(LetterTemplate)
|
|
||||||
class LetterTemplateAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Класс админки для модели LetterTemplate.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Поля, которые будут отображаться в списке записей
|
|
||||||
list_display = (
|
|
||||||
"subject", # Тема письма
|
|
||||||
"type", # Тип письма
|
|
||||||
"copy_to", # Копия адресата
|
|
||||||
"body_preview", # Кастомное поле для предпросмотра тела письма
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поля, по которым можно выполнять поиск
|
|
||||||
search_fields = (
|
|
||||||
"subject",
|
|
||||||
"body",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Фильтры для боковой панели
|
|
||||||
list_filter = ("type",)
|
|
||||||
|
|
||||||
# Поля, которые будут показаны в форме редактирования
|
|
||||||
fieldsets = (
|
|
||||||
(
|
|
||||||
"Основная информация",
|
|
||||||
{
|
|
||||||
"fields": ("subject", "type", "copy_to"),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Тело письма",
|
|
||||||
{
|
|
||||||
"fields": ("body",),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@admin.display(description="Текст письма (предпросмотр)", empty_value="—")
|
|
||||||
def body_preview(self, obj):
|
|
||||||
"""
|
|
||||||
Отображает краткий предпросмотр тела письма.
|
|
||||||
"""
|
|
||||||
return obj.body[:50] + "..." if obj.body and len(obj.body) > 50 else obj.body
|
|
||||||
|
|
|
@ -2,55 +2,10 @@ from slugify import slugify
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.validators import MaxValueValidator
|
from django.core.validators import MaxValueValidator
|
||||||
|
|
||||||
from markdownfield.models import MarkdownField, RenderedMarkdownField
|
from global_data.models import Ref1C
|
||||||
from markdownfield.validators import VALIDATOR_STANDARD
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("root")
|
|
||||||
|
|
||||||
|
|
||||||
def group_based_upload_to(instance, filename):
|
|
||||||
logger.info(instance)
|
|
||||||
return "files/image/{}/{}/{}".format(
|
|
||||||
type(instance).__name__.lower(), instance.id, filename
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Seo(models.Model):
|
|
||||||
"""
|
|
||||||
Модель SEO содержит информацию о тексте, электронной почте, дополнительном тексте и изображении.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Первичный ключ, автоинкрементируемый
|
|
||||||
id = models.AutoField(primary_key=True, verbose_name="ID")
|
|
||||||
|
|
||||||
# Поле для хранения текста с максимальной длиной 200 символов
|
|
||||||
text = models.CharField(
|
|
||||||
max_length=200, help_text="Введите текст", verbose_name="Текст"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Поле для хранения email-адреса
|
|
||||||
email = models.EmailField(help_text="Введите Email", verbose_name="Email")
|
|
||||||
|
|
||||||
# Поле для хранения дополнительного текста, может быть пустым
|
|
||||||
blank = models.TextField(blank=True, null=True, verbose_name="Дополнительный текст")
|
|
||||||
|
|
||||||
# Поле для хранения изображения, загружается по указанному пути
|
|
||||||
image = models.ImageField(
|
|
||||||
upload_to=group_based_upload_to,
|
|
||||||
default="", # Пустая строка в качестве значения по умолчанию
|
|
||||||
verbose_name="Изображение",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.text}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "SEO запись"
|
|
||||||
verbose_name_plural = "SEO записи"
|
|
||||||
|
|
||||||
|
|
||||||
|
from mns.logger import logger
|
||||||
|
from mns.utils import group_based_upload_to
|
||||||
|
|
||||||
class KupiZabor(models.Model):
|
class KupiZabor(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -114,151 +69,34 @@ class KupiZabor(models.Model):
|
||||||
verbose_name_plural = "Страницы 'KupiZabor'"
|
verbose_name_plural = "Страницы 'KupiZabor'"
|
||||||
|
|
||||||
|
|
||||||
class Page(models.Model):
|
class AdvantageModel(models.Model):
|
||||||
"""
|
"""
|
||||||
Модель PageModel содержит информацию о странице, включая порядок отображения,
|
Модель AdvantageModel содержит информацию о преимуществах, включая заголовок и содержимое.
|
||||||
заголовки меню и страницы, содержимое, слаг, изображение и внешнюю ссылку.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Порядок отображения страницы
|
# Заголовок
|
||||||
order = models.IntegerField(
|
|
||||||
verbose_name="Порядок",
|
|
||||||
help_text="Введите порядковый номер страницы для сортировки",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Заголовок меню
|
|
||||||
menu_title = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
verbose_name="Заголовок меню",
|
|
||||||
help_text="Введите заголовок, который будет отображаться в меню",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Заголовок страницы
|
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
max_length=100,
|
|
||||||
verbose_name="Заголовок страницы",
|
|
||||||
help_text="Введите заголовок страницы",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Содержимое страницы в формате Markdown
|
|
||||||
content = MarkdownField(
|
|
||||||
rendered_field="content_rendered",
|
|
||||||
validator=VALIDATOR_STANDARD,
|
|
||||||
verbose_name="Содержимое (Markdown)",
|
|
||||||
help_text="Введите содержимое страницы в формате Markdown",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Отрендеренное содержимое страницы
|
|
||||||
content_rendered = RenderedMarkdownField(
|
|
||||||
verbose_name="Отрендеренное содержимое",
|
|
||||||
help_text="Это поле автоматически генерируется из поля 'Содержимое'",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Slug для URL страницы
|
|
||||||
slug = models.SlugField(
|
|
||||||
unique=True,
|
|
||||||
verbose_name="Slug",
|
|
||||||
help_text="Введите уникальный slug для URL страницы",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Изображение страницы
|
|
||||||
image = models.ImageField(
|
|
||||||
upload_to=group_based_upload_to,
|
|
||||||
default=None, # Убедитесь, что это значение корректно
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name="Изображение",
|
|
||||||
help_text="Выберите изображение для страницы",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Внешняя ссылка
|
|
||||||
external_link = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name="Внешняя ссылка",
|
|
||||||
help_text="Введите внешнюю ссылку, если она есть",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.menu_title} #{self.slug} {self.order}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Страница"
|
|
||||||
verbose_name_plural = "Страницы"
|
|
||||||
|
|
||||||
|
|
||||||
class Menu(models.Model):
|
|
||||||
"""
|
|
||||||
Модель Menu содержит информацию о меню, включая страницы и тип меню.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Связь с моделью PageModel (многие ко многим)
|
|
||||||
pages = models.ManyToManyField(
|
|
||||||
PageModel,
|
|
||||||
verbose_name="Страницы",
|
|
||||||
help_text="Выберите страницы, которые будут включены в меню",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Тип меню (выбор из списка)
|
|
||||||
type = models.IntegerField(
|
|
||||||
choices=[
|
|
||||||
(1, "TopMenu"),
|
|
||||||
],
|
|
||||||
unique=True,
|
|
||||||
verbose_name="Тип меню",
|
|
||||||
help_text="Выберите тип меню",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Возвращает строковое представление объекта модели.
|
|
||||||
"""
|
|
||||||
menu_type = dict(self._meta.get_field("type").choices).get(
|
|
||||||
self.type, "Неизвестно"
|
|
||||||
)
|
|
||||||
return f"Меню {menu_type}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Меню"
|
|
||||||
verbose_name_plural = "Меню"
|
|
||||||
|
|
||||||
|
|
||||||
class ReviewModel(models.Model):
|
|
||||||
"""
|
|
||||||
Модель ReviewModel содержит информацию о отзывах, включая изображение, текст и комментарий.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Изображение отзыва
|
|
||||||
image = models.ImageField(
|
|
||||||
upload_to=group_based_upload_to,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name="Изображение",
|
|
||||||
help_text="Выберите изображение для отзыва",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Текст отзыва
|
|
||||||
text = models.CharField(
|
|
||||||
max_length=200, # Установите максимальную длину строки
|
max_length=200, # Установите максимальную длину строки
|
||||||
verbose_name="Текст",
|
verbose_name="Заголовок",
|
||||||
help_text="Введите текст отзыва",
|
help_text="Введите заголовок",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Комментарий к отзыву
|
# Содержимое
|
||||||
comment = models.TextField(
|
content = models.CharField(
|
||||||
verbose_name="Комментарий", help_text="Введите комментарий к отзыву"
|
max_length=200, # Установите максимальную длину строки
|
||||||
|
verbose_name="Содержимое",
|
||||||
|
help_text="Введите содержимое",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
Возвращает строковое представление объекта модели.
|
Возвращает строковое представление объекта модели.
|
||||||
"""
|
"""
|
||||||
return f"{self.text}"
|
return f"{self.title} {self.content}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Отзыв"
|
verbose_name = "Преимущество"
|
||||||
verbose_name_plural = "Отзывы"
|
verbose_name_plural = "Преимущества"
|
||||||
|
|
||||||
|
|
||||||
class DiscountModel(models.Model):
|
class DiscountModel(models.Model):
|
||||||
|
@ -302,51 +140,6 @@ class DiscountModel(models.Model):
|
||||||
verbose_name_plural = "Скидки"
|
verbose_name_plural = "Скидки"
|
||||||
|
|
||||||
|
|
||||||
class Ref1C(models.Model):
|
|
||||||
"""
|
|
||||||
Модель для хранения ссылки на данные из 1С (Ref_Key), флага для включения синхронизации,
|
|
||||||
последней синхронизированной цены и значения по умолчанию.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ref_key = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name="Ref_Key",
|
|
||||||
help_text="Уникальный идентификатор из 1С",
|
|
||||||
)
|
|
||||||
sync_enabled = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
verbose_name="Включить синхронизацию",
|
|
||||||
help_text="Флаг для включения синхронизации данных из 1С",
|
|
||||||
)
|
|
||||||
default_value = models.DecimalField(
|
|
||||||
max_digits=7,
|
|
||||||
decimal_places=2,
|
|
||||||
verbose_name="Значение по умолчанию",
|
|
||||||
help_text="Значение, используемое по умолчанию или при отсутствии данных из 1С",
|
|
||||||
)
|
|
||||||
last_sync_price = models.DecimalField(
|
|
||||||
max_digits=7,
|
|
||||||
decimal_places=2,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name="Последняя синхронизированная цена",
|
|
||||||
help_text="Последняя цена, полученная из 1С",
|
|
||||||
)
|
|
||||||
updated_at = models.DateTimeField(
|
|
||||||
auto_now=True,
|
|
||||||
verbose_name="Последнее обновление",
|
|
||||||
help_text="Дата и время последнего обновления записи"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Ref_Key: {self.ref_key}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Ссылка на 1С"
|
|
||||||
verbose_name_plural = "Ссылки на 1С"
|
|
||||||
|
|
||||||
|
|
||||||
class CalcModel(models.Model):
|
class CalcModel(models.Model):
|
||||||
"""
|
"""
|
||||||
Модель CalcModel содержит информацию о калькуляторе стоимости, включая заголовок,
|
Модель CalcModel содержит информацию о калькуляторе стоимости, включая заголовок,
|
||||||
|
@ -462,202 +255,3 @@ class CalcModel(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Калькулятор стоимости"
|
verbose_name = "Калькулятор стоимости"
|
||||||
verbose_name_plural = "Калькуляторы стоимости"
|
verbose_name_plural = "Калькуляторы стоимости"
|
||||||
|
|
||||||
|
|
||||||
class AdvantageModel(models.Model):
|
|
||||||
"""
|
|
||||||
Модель AdvantageModel содержит информацию о преимуществах, включая заголовок и содержимое.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Заголовок
|
|
||||||
title = models.CharField(
|
|
||||||
max_length=200, # Установите максимальную длину строки
|
|
||||||
verbose_name="Заголовок",
|
|
||||||
help_text="Введите заголовок",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Содержимое
|
|
||||||
content = models.CharField(
|
|
||||||
max_length=200, # Установите максимальную длину строки
|
|
||||||
verbose_name="Содержимое",
|
|
||||||
help_text="Введите содержимое",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Возвращает строковое представление объекта модели.
|
|
||||||
"""
|
|
||||||
return f"{self.title} {self.content}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Преимущество"
|
|
||||||
verbose_name_plural = "Преимущества"
|
|
||||||
|
|
||||||
|
|
||||||
class FooterModel(models.Model):
|
|
||||||
"""
|
|
||||||
Модель FooterModel содержит информацию о нижнем колонтитуле, включая текст и флаг малого текста.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Текст нижнего колонтитула
|
|
||||||
text = models.TextField(
|
|
||||||
max_length=1000,
|
|
||||||
verbose_name="Текст",
|
|
||||||
help_text="Введите текст нижнего колонтитула",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Флаг малого текста
|
|
||||||
small_text = models.BooleanField(
|
|
||||||
verbose_name="Малый текст",
|
|
||||||
help_text="Отметьте, если текст должен быть маленьким",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Возвращает строковое представление объекта модели.
|
|
||||||
"""
|
|
||||||
return f"{self.text}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Нижний колонтитул"
|
|
||||||
verbose_name_plural = "Нижние колонтитулы"
|
|
||||||
|
|
||||||
|
|
||||||
class CustomRequest(models.Model):
|
|
||||||
"""
|
|
||||||
Модель CustomRequest содержит информацию о пользовательских запросах,
|
|
||||||
включая имя, телефон, email, дату создания, информацию о заборе и согласие на политику конфиденциальности.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Имя пользователя
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=100, verbose_name="Имя", help_text="Введите имя пользователя"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Телефон пользователя (необязательное поле)
|
|
||||||
phone = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name="Телефон",
|
|
||||||
help_text="Введите телефон пользователя",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Email пользователя (необязательное поле)
|
|
||||||
email = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name="Email",
|
|
||||||
help_text="Введите email пользователя",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Дата и время создания запроса
|
|
||||||
date = models.DateTimeField(
|
|
||||||
auto_now_add=True,
|
|
||||||
verbose_name="Дата создания",
|
|
||||||
help_text="Дата и время создания запроса",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Информация о заборе
|
|
||||||
fence_info = models.TextField(
|
|
||||||
verbose_name="Информация о заборе", help_text="Введите информацию о заборе"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Согласие на политику конфиденциальности
|
|
||||||
privacy = models.BooleanField(
|
|
||||||
verbose_name="Политика конфиденциальности",
|
|
||||||
help_text="Отметьте, если пользователь согласен с политикой конфиденциальности",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Возвращает строковое представление объекта модели.
|
|
||||||
"""
|
|
||||||
return f"{self.name} {self.phone} {self.date}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Пользовательский запрос"
|
|
||||||
verbose_name_plural = "Пользовательские запросы"
|
|
||||||
|
|
||||||
|
|
||||||
class SocialNetwork(models.Model):
|
|
||||||
"""
|
|
||||||
Модель SocialNetwork содержит информацию о социальных сетях, включая название, ссылку и иконку.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Название социальной сети
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=50,
|
|
||||||
verbose_name="Название",
|
|
||||||
help_text="Введите название социальной сети",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ссылка на социальную сеть
|
|
||||||
link = models.CharField(
|
|
||||||
max_length=50,
|
|
||||||
verbose_name="Ссылка",
|
|
||||||
help_text="Введите ссылку на социальную сеть",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Иконка социальной сети (необязательное поле)
|
|
||||||
icon = models.TextField(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name="Иконка",
|
|
||||||
help_text="Введите HTML-код или путь к иконке социальной сети",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Возвращает строковое представление объекта модели.
|
|
||||||
"""
|
|
||||||
return f"{self.name}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Социальная сеть"
|
|
||||||
verbose_name_plural = "Социальные сети"
|
|
||||||
|
|
||||||
|
|
||||||
class LetterTemplate(models.Model):
|
|
||||||
"""
|
|
||||||
Модель LetterTemplate содержит информацию о шаблонах писем, включая тему, тело письма, копию адресата и тип письма.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Тема письма
|
|
||||||
subject = models.CharField(
|
|
||||||
max_length=100, verbose_name="Тема письма", help_text="Введите тему письма"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Тело письма
|
|
||||||
body = models.TextField(
|
|
||||||
verbose_name="Текст письма", help_text="Введите текст письма"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Копия адресата (необязательное поле)
|
|
||||||
copy_to = models.EmailField(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name="Копия адресата",
|
|
||||||
help_text="Введите email для копии письма",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Тип письма (выбор из списка)
|
|
||||||
type = models.CharField(
|
|
||||||
choices=[
|
|
||||||
("user_submit", "Submit"),
|
|
||||||
],
|
|
||||||
unique=True,
|
|
||||||
verbose_name="Тип письма",
|
|
||||||
help_text="Выберите тип письма",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Возвращает строковое представление объекта модели.
|
|
||||||
"""
|
|
||||||
return f"{self.subject}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Шаблон письма"
|
|
||||||
verbose_name_plural = "Шаблоны писем"
|
|
||||||
|
|
|
@ -1,28 +1,10 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import (
|
from .models import (
|
||||||
AdvantageModel,
|
|
||||||
CalcModel,
|
CalcModel,
|
||||||
CustomRequest,
|
|
||||||
FooterModel,
|
|
||||||
KupiZabor,
|
KupiZabor,
|
||||||
LetterTemplate,
|
AdvantageModel
|
||||||
PageModel,
|
|
||||||
ReviewModel,
|
|
||||||
Seo,
|
|
||||||
SocialNetwork,
|
|
||||||
Menu,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("root")
|
|
||||||
|
|
||||||
|
|
||||||
class SeoSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Seo
|
|
||||||
fields = ["id", "text", "email", "blank", "image"]
|
|
||||||
|
|
||||||
|
|
||||||
class KPSerializer(serializers.ModelSerializer):
|
class KPSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -30,63 +12,13 @@ class KPSerializer(serializers.ModelSerializer):
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class PageSerializer(serializers.ModelSerializer):
|
|
||||||
image = serializers.ImageField(use_url=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = PageModel
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
class MenuSerializer(serializers.ModelSerializer):
|
|
||||||
pages = PageSerializer(many=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Menu
|
|
||||||
fields = "__all__"
|
|
||||||
depth = 2
|
|
||||||
|
|
||||||
|
|
||||||
class ReviewSerializer(serializers.ModelSerializer):
|
|
||||||
image = serializers.ImageField(use_url=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ReviewModel
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
class CalcSerializer(serializers.ModelSerializer):
|
class CalcSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CalcModel
|
model = CalcModel
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
depth = 2
|
depth = 2
|
||||||
|
|
||||||
|
|
||||||
class AdvantageSerializer(serializers.ModelSerializer):
|
class AdvantageSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdvantageModel
|
model = AdvantageModel
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class FooterSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = FooterModel
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
class CustomRequestSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = CustomRequest
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
class SocialNetworkSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = SocialNetwork
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
class LetterTemplateSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = LetterTemplate
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
|
@ -1,51 +1,18 @@
|
||||||
import re
|
from rest_framework import viewsets
|
||||||
import requests
|
|
||||||
from datetime import datetime, timedelta
|
from mns.logger import logger
|
||||||
from django.conf import settings
|
|
||||||
from rest_framework import viewsets, filters
|
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
AdvantageModel,
|
|
||||||
CalcModel,
|
CalcModel,
|
||||||
CustomRequest,
|
|
||||||
FooterModel,
|
|
||||||
KupiZabor,
|
KupiZabor,
|
||||||
LetterTemplate,
|
AdvantageModel
|
||||||
PageModel,
|
|
||||||
ReviewModel,
|
|
||||||
Seo,
|
|
||||||
SocialNetwork,
|
|
||||||
Menu,
|
|
||||||
)
|
)
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
AdvantageSerializer,
|
|
||||||
CalcSerializer,
|
CalcSerializer,
|
||||||
CustomRequestSerializer,
|
|
||||||
FooterSerializer,
|
|
||||||
KPSerializer,
|
KPSerializer,
|
||||||
LetterTemplateSerializer,
|
AdvantageSerializer
|
||||||
PageSerializer,
|
|
||||||
ReviewSerializer,
|
|
||||||
SeoSerializer,
|
|
||||||
SocialNetworkSerializer,
|
|
||||||
MenuSerializer,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("root")
|
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
class SeoViewSet(viewsets.ModelViewSet):
|
|
||||||
"""
|
|
||||||
API endpoint that allows partners to be viewed or edited.
|
|
||||||
"""
|
|
||||||
|
|
||||||
http_method_names = ["get"]
|
|
||||||
queryset = Seo.objects.all()
|
|
||||||
serializer_class = SeoSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class KPViewSet(viewsets.ModelViewSet):
|
class KPViewSet(viewsets.ModelViewSet):
|
||||||
http_method_names = ["get"]
|
http_method_names = ["get"]
|
||||||
|
@ -53,118 +20,12 @@ class KPViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = KPSerializer
|
serializer_class = KPSerializer
|
||||||
|
|
||||||
|
|
||||||
class PageViewSet(viewsets.ModelViewSet):
|
|
||||||
http_method_names = ["get"]
|
|
||||||
queryset = PageModel.objects.all()
|
|
||||||
serializer_class = PageSerializer
|
|
||||||
filter_backends = [filters.OrderingFilter]
|
|
||||||
lookup_field = 'slug'
|
|
||||||
|
|
||||||
|
|
||||||
class MenuViewSet(viewsets.ModelViewSet):
|
|
||||||
http_method_names = ["get"]
|
|
||||||
queryset = Menu.objects.all()
|
|
||||||
serializer_class = MenuSerializer
|
|
||||||
filter_backends = [filters.OrderingFilter]
|
|
||||||
|
|
||||||
|
|
||||||
class ReviewViewSet(viewsets.ModelViewSet):
|
|
||||||
http_method_names = ["get"]
|
|
||||||
queryset = ReviewModel.objects.all()
|
|
||||||
serializer_class = ReviewSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class CalcViewSet(viewsets.ModelViewSet):
|
class CalcViewSet(viewsets.ModelViewSet):
|
||||||
http_method_names = ["get"]
|
http_method_names = ["get"]
|
||||||
queryset = CalcModel.objects.all()
|
queryset = CalcModel.objects.all()
|
||||||
serializer_class = CalcSerializer
|
serializer_class = CalcSerializer
|
||||||
|
|
||||||
|
|
||||||
class AdvantageViewSet(viewsets.ModelViewSet):
|
class AdvantageViewSet(viewsets.ModelViewSet):
|
||||||
http_method_names = ["get"]
|
http_method_names = ["get"]
|
||||||
queryset = AdvantageModel.objects.all()
|
queryset = AdvantageModel.objects.all()
|
||||||
serializer_class = AdvantageSerializer
|
serializer_class = AdvantageSerializer
|
||||||
|
|
||||||
|
|
||||||
class FooterViewSet(viewsets.ModelViewSet):
|
|
||||||
http_method_names = ["get"]
|
|
||||||
queryset = FooterModel.objects.all()
|
|
||||||
serializer_class = FooterSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class CustomRequestViewSet(viewsets.ModelViewSet):
|
|
||||||
http_method_names = ["options", "post"]
|
|
||||||
authentication_classes = ()
|
|
||||||
queryset = CustomRequest.objects.all()
|
|
||||||
serializer_class = CustomRequestSerializer
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
serializer.save()
|
|
||||||
name = self.request.data.get("name")
|
|
||||||
phone = self.request.data.get("phone")
|
|
||||||
email = self.request.data.get("email")
|
|
||||||
fence_info = self.request.data.get("fence_info")
|
|
||||||
total = re.findall(r"Итого.*?(\d+\s?\d+,?\d+).*?₽", fence_info)
|
|
||||||
total_price = 0 if not len(total) else re.sub(r"\s", "", "" or total[0])
|
|
||||||
data = {
|
|
||||||
"fields[TITLE]": f"КУПИЗАБОР {name} {email} {phone}",
|
|
||||||
"fields[NAME]": name,
|
|
||||||
"fields[STATUS_ID]": "NEW",
|
|
||||||
"fields[OPENED]": "Y",
|
|
||||||
# "fields[ASSIGNED_BY_ID]": "34",
|
|
||||||
"fields[PHONE][0][VALUE]": phone,
|
|
||||||
"fields[PHONE][0][VALUE_TYPE]": "WORK",
|
|
||||||
"fields[EMAIL][0][VALUE]": email,
|
|
||||||
"fields[EMAIL][0][VALUE_TYPE]": "WORK",
|
|
||||||
"fields[SOURCE_ID]": "1",
|
|
||||||
"fields[OPPORTUNITY]": total_price,
|
|
||||||
"fields[UF_CRM_1713510698]": fence_info,
|
|
||||||
"fields[UF_CRM_1712154805]": 47,
|
|
||||||
"fields[UF_CRM_1712154416]": datetime.now() + timedelta(days=1),
|
|
||||||
}
|
|
||||||
bx_result = requests.post(
|
|
||||||
f"{settings.BX_WEBHOOK}/crm.lead.add.json",
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
bx_result_json = bx_result.json()
|
|
||||||
if bx_result_json["result"]:
|
|
||||||
logger.info(f'Был создан лид {bx_result_json["result"]}')
|
|
||||||
letter_template = LetterTemplate.objects.get(type="user_submit")
|
|
||||||
if email:
|
|
||||||
todo_data = {
|
|
||||||
"fields[OWNER_TYPE_ID]": 1,
|
|
||||||
"fields[OWNER_ID]": bx_result_json["result"],
|
|
||||||
"fields[TYPE_ID]": 4,
|
|
||||||
"fields[SUBJECT]": (
|
|
||||||
"КУПИЗАБОР" if not letter_template else letter_template.subject
|
|
||||||
),
|
|
||||||
"fields[DESCRIPTION]": (
|
|
||||||
f"Данные расчета\n{fence_info}"
|
|
||||||
if not letter_template
|
|
||||||
else re.sub("\[fence\]", fence_info, letter_template.body)
|
|
||||||
),
|
|
||||||
"fields[DESCRIPTION_TYPE]": 2,
|
|
||||||
"fields[COMMUNICATIONS][0][VALUE]": email,
|
|
||||||
"fields[COMPLETED]": "Y",
|
|
||||||
"fields[DIRECTION]": 2,
|
|
||||||
"fields[SETTINGS][MESSAGE_FROM]": "СВС технологии <office@svs-tech.pro>",
|
|
||||||
}
|
|
||||||
bx_result_email = requests.post(
|
|
||||||
f"{settings.BX_WEBHOOK}/crm.activity.add",
|
|
||||||
todo_data,
|
|
||||||
)
|
|
||||||
email_result = bx_result_email.json()
|
|
||||||
if email_result:
|
|
||||||
logger.info(f"Было отправлено письмо {email_result['result']}")
|
|
||||||
|
|
||||||
|
|
||||||
class SocialNetworkViewSet(viewsets.ModelViewSet):
|
|
||||||
http_method_names = ["get"]
|
|
||||||
queryset = SocialNetwork.objects.all()
|
|
||||||
serializer_class = SocialNetworkSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class LetterTemplatesViewSet(viewsets.ModelViewSet):
|
|
||||||
http_method_names = ["get"]
|
|
||||||
queryset = LetterTemplate.objects.all()
|
|
||||||
serializer_class = LetterTemplateSerializer
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("root")
|
|
@ -50,6 +50,7 @@ CSRF_TRUSTED_ORIGINS = [
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"corsheaders",
|
"corsheaders",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
|
"global_data",
|
||||||
"kupizabor",
|
"kupizabor",
|
||||||
"markdownfield",
|
"markdownfield",
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
|
|
23
mns/urls.py
23
mns/urls.py
|
@ -20,20 +20,21 @@ from django.urls import include, path
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from kupizabor import views
|
from kupizabor import views as kp_views
|
||||||
|
from global_data import views as gd_views
|
||||||
|
|
||||||
site_prefix = "kp"
|
site_prefix = "kp"
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(f"{site_prefix}/seo", views.SeoViewSet)
|
router.register(f"{site_prefix}/seo", gd_views.SeoViewSet)
|
||||||
router.register(f"{site_prefix}/kp", views.KPViewSet)
|
router.register(f"{site_prefix}/kp", kp_views.KPViewSet)
|
||||||
router.register(f"{site_prefix}/pages", views.PageViewSet)
|
router.register(f"{site_prefix}/pages", gd_views.PageViewSet)
|
||||||
router.register(f"{site_prefix}/menu", views.MenuViewSet)
|
router.register(f"{site_prefix}/menu", gd_views.MenuViewSet)
|
||||||
router.register(f"{site_prefix}/review", views.ReviewViewSet)
|
router.register(f"{site_prefix}/review", gd_views.ReviewViewSet)
|
||||||
router.register(f"{site_prefix}/calculator", views.CalcViewSet)
|
router.register(f"{site_prefix}/calculator", kp_views.CalcViewSet)
|
||||||
router.register(f"{site_prefix}/advantage", views.AdvantageViewSet)
|
router.register(f"{site_prefix}/advantage", kp_views.AdvantageViewSet)
|
||||||
router.register(f"{site_prefix}/footer", views.FooterViewSet)
|
router.register(f"{site_prefix}/footer", gd_views.FooterViewSet)
|
||||||
router.register(f"{site_prefix}/custom_request", views.CustomRequestViewSet)
|
router.register(f"{site_prefix}/custom_request", gd_views.CustomRequestViewSet)
|
||||||
router.register(f"{site_prefix}/social_network", views.SocialNetworkViewSet)
|
router.register(f"{site_prefix}/social_network", gd_views.SocialNetworkViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include(router.urls)),
|
path("", include(router.urls)),
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
from mns.logger import logger
|
||||||
|
|
||||||
|
def group_based_upload_to(instance, filename):
|
||||||
|
logger.info(instance)
|
||||||
|
return "files/image/{}/{}/{}".format(
|
||||||
|
type(instance).__name__.lower(), instance.id, filename
|
||||||
|
)
|
Loading…
Reference in New Issue