Дуже давно хотів написати статтю про те, як написати функціонал 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
Для підтримки всього цього функціоналу необхідно встановити бібліотеки:
- Уцінка
- MarkdownSubscript
- Позначка Надіндекс
Можливо ще 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 контент.