Очень давно хотел написать статью о том, как написать функционал auto populate field для проекта на Django . Это очень полезный функционал, который позволяет изменять содержимое других полей модели в Django при установке значение в то поле, в котором используется auto populate.
Во-первых, зачем это нужно? - Подобный функционал позволяет сократить размеры кода в тех местах, где нужно переписывать другие поля объекта, при их изменении. То есть, вам например не придется каждый раз переопределять метод save, чтобы переписать какое-то поле в случае изменения других полей объекта. Также использование auto populate в принципе более продвинутый и аккуратный способ управления моделями данных в Django.
А также подобный подход моет решить некоторые проблемы и улучшить работу сайта.
Описание подхода на примере markdown полей
Например такой подход я использую на этом сайте для написания контента комментариев и статей, и вообще во всех полях, где используется синтаксис markdown .
Как это работает?
- Все модели данных имеют два поля content и content_markdown . Поле content_markdown управляет содержимым поля content . То есть в обычной ситуации пользователь не имеет доступа к редактирования содержимого поля content , он всегда редактирует только поле content_markdown .
- Пользователь, создавая сообщение, редактирует поле content_markdown , которое автоматически генерирут html код из markdown разметки и записывает html в поле content .
Зачем это нужно?
Каждый сам выбирает, для чего ему использовать auto populate. Например, Вы можете автоматически генерировать thumbnail изображение из исходного изображения. Подобный функционал идеально подходит для таких действий.
А конкретно в моём случае получилось так, что я не нашёл подходящего функционала для генерирования html из markdown. Всё что я находил на тот момент, это библиотеки, которые генерировал html в runtime при каждой загрузке страницы с помощью шаблонных тегов. А подобный подход очень сильно снижает производительность сайта. Поэтому я для себя решил, что готов пожертвовать дисковым пространством ради производительности сайта. В конце концов десятикратное увеличение скорости загрузки страницы в некоторых случаях явно предпочтительнее, чем двойной объем занимаемого дискового пространства.
Реализация
Я представлю упрощенный код MarkdownField , который вы могли бы использовать у себя на сайте.
MarkdownField
MarkdownField будет наследоваться от стандартного TextField и будет получать в качестве аргумента имя поля, которое будет использовать для html контента. Это аргумент html_field .
Данное поле имеет метод set_html , который отвечает за генерирование html из markdown разметки, а также установку значения в поле html контента.
Важным моментом является подключение метода set_html к сигналу pre_save в методе contribute_to_class . Более ранний вариант, который я разрабатывал, имел такой недостаток, что при каждом запросе объекта вызывался метод генерирования html. Это была ошибка, которую я впоследствие обнаружил и исправил подобным образом. Теперь контент генерируется только в случае сохранения объекта.
# -*- coding: utf-8 -*- from django.db import models from django.db.models.signals import pre_save from .utils import MarkdownWorker class MarkdownField(models.TextField): """ This field save markdown text with auto-populate text to html field. This field must be used with second text field for html content. """ def set_html(self, instance=None, update_fields=None, **kwargs): value = getattr(instance, self.attname) if value and len(value) > 0: instance.__dict__[self.html_field] = MarkdownWorker(value).get_text() def contribute_to_class(self, cls, name, **kwargs): super().contribute_to_class(cls, name, **kwargs) pre_save.connect(self.set_html, sender=cls) def __init__(self, html_field=None, *args, **kwargs): self.html_field = html_field super().__init__(*args, **kwargs)
MarkdownWorker
Даннй класс отввечает за генерирование html контента из markdown разметки. В данном классе вы можете добавить свои любые дополнительные виды обработки из html, чтобы получить необходимы вам готовый результат. Например, вы можете удалять ссылки или изображения, которые, по вашему мнению, пользователь не может добавлять.
При создании объекта класса MarkdownWorker устанавливается текст для обработки, а далее вызывается метод, который преобразует текст в html.
Также в моём случае используются расширения для:
- Генерирования таблиц
- Поддержки кода
- Добавления переноса
- Верхнего и нижнего синтаксиса
# -*- coding: utf-8 -*- import markdown class MarkdownWorker: """ Markdown converter. It will convert markdown text to html text """ def __init__(self, text): self.pre_markdown_text = text self.markdown_text = None self.make_html_from_markdown() def make_html_from_markdown(self): if self.pre_markdown_text: self.markdown_text = markdown.markdown( self.pre_markdown_text, extensions=['markdown.extensions.attr_list', 'markdown.extensions.tables', 'markdown.extensions.fenced_code', 'markdown.extensions.nl2br', 'superscript', 'subscript'], output_format='html5' ) def get_text(self): return self.markdown_text
Для поддержки всего этого функционала вам необходимо будет установить библиотеки:
- Markdown
- MarkdownSubscript
- MarkdownSuperscript
Возможно ещё beautifulsoup4 , либо это установится в зависимостях, а так честно, не помню.
Применение
А теперь применим данное поле в модели данных.
У нас имеется модель Post, в которой находятся поля:
- user - внешний ключ на пользователя
- content - поле, в котором будет содержаться html контент
- content_markdown - поле, в которое будет сохраняться markdown текст с последующей генерацией html текста. При этом вы видите, что в него передаётся аргумент html_field='content', который говорит, с каким полем предстоит работать MarkdownField.
# -*- coding: utf-8 -*- from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ from .fields import MarkdownField class Post(models.Model): user = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE) content = models.TextField(verbose_name=_('Description')) content_markdown = MarkdownField(verbose_name=_('Description - Markdown'), html_field='content')
Заключение
Таким образом вы сможете добавить на свой сайт markdown поля, которые будут автоматически генерировать html контент.
Теперь у вас будет поддержка модного markdown синтаксиса на вашем сайте, который автоматически создаст html контент.