new model logic

This commit is contained in:
Kseninia Mikhaylova 2025-03-12 11:00:31 +03:00
parent b22fcd0d4d
commit 93636efecf
16 changed files with 1103 additions and 1118 deletions

0
global_data/__init__.py Normal file
View File

431
global_data/admin.py Normal file
View File

@ -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

6
global_data/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class GlobalDataConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'global_data'

View File

402
global_data/models.py Normal file
View File

@ -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 = "Шаблоны писем"

View File

@ -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__"

3
global_data/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

144
global_data/views.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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 = "Шаблоны писем"

View File

@ -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__"

View File

@ -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

4
mns/logger.py Normal file
View File

@ -0,0 +1,4 @@
import logging
logger = logging.getLogger("root")

View File

@ -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",

View File

@ -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)),

8
mns/utils.py Normal file
View File

@ -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
)