Evgenii Legotckoi
Evgenii LegotckoiМамыр 9, 2020, 6:09 Т.Ж.

Django - Оқулық 054. Полиморфты динамикалық виджет жүйесін құру жолы

Бір күні мен сайтқа динамикалық виджеттерді қосу мәселесін шешіп, виджеттердің әртүрлі түрлерін қосу мүмкіндігімен, сондай-ақ нақты іске асыру үлгілерін қосу арқылы виджеттер жиынтығын одан әрі шектеусіз кеңейту мүмкіндігімен шештім.

Жүйе бір Виджет класы болатындай, ол жасалған кезде таңдалған түріне байланысты нақты іске асыру үлгісіне жатады, мысалы, кейбір арнайы параметрлерді қамтитын HTMLWidgetImpl , сонымен қатар оның рендеринг үлгісі туралы біледі. render() әдісі деп аталатын сайт үлгісіндегі виджет түрі арқылы белгілі бір іске асыруға және оның render()** әдісіне сілтеме жасайды.

Бұл тәсіл шаблон үшін өте қарапайым кодты жазуға, сондай-ақ кодты жаңа виджеттер қосу деректердің жаңа үлгісін қосудан, сондай-ақ кодтағы бірқатар орындарды өңдеуден тұратындай етіп ұйымдастыруға мүмкіндік береді, дәлірек айтсақ оларды жаңарту. Бұл, шын мәнінде, күнделікті тапсырмаға айналады және бағдарламашының жаңа нәрсе ойлап табу қажеттілігін жояды.

Яғни, мен бұл мақалада тек программалық код деңгейіндегі есептің шешімін ғана емес, сондай-ақ осындай есептерді шешу кезінде архитектураны жоспарлаудың тәсілін көрсеткім келеді.


Тапсырмаларды орнату

Сондықтан сайтқа сайттың бүйірлік тақтасында көрсетілетін динамикалық виджеттер жүйесін енгізу қажет. Бұл ретте біз виджеттердің екі түрінен бастағымыз келеді, біреуінде көрсетілетін html коды ғана болады, ал екіншісінде алдын ала анықталған үлгіге көрсетілетін мазмұнның тақырыбы мен html коды болады. Сонымен қатар, бүйірлік тақтада сурет салу үшін біз виджеттердің ретін орната білуіміз керек.

Сондықтан біз жаңа қосымшаны жасаймыз

python manage.py startapp evileg_widgets

Содан кейін біз барлық қажетті модельдерді жасаймыз және бізге келесі үлгілер қажет

  • SideBar - бүйірлік тақта үлгісі, айтпақшы, мен оны Singleton үлгісі ретінде құрастырамын
  • Виджет - полиморфты виджет үлгісі
  • HTMLWidgetImpl - қарапайым HTML кодын енгізуге арналған нақты виджетті іске асыру
  • StandardWidgetImpl - көрсету кодын енгізуге арналған арнайы виджетті іске асыру
  • SideBarToWidget - SideBar үлгісі мен виджет үлгісі арасындағы ManyToMany өрістеріне арналған үлгі

үлгілер

Біз жасайтын модельдерден бастайық.

Бүйірлік тақта

SideBar үлгісі үшін мен бір данада болуы керек теңшеу нысандарын қалыптастыруға арналған django-solo үшінші тарап қолданбасынан SingletonModel пайдаланамын.
SideBar үлгісінде торапқа қосылған виджеттер жиынына жауап беретін виджеттер өрісі бар, бірақ ManyToMany қатынасы SideBarToWidget аралық үлгісі арқылы құрылады, ол виджеттердің ретін SideBar-ға қосады.

# -*- coding: utf-8 -*-

from django.db import models
from django.template.loader import render_to_string
from django.utils.translation import ugettext, ugettext_lazy as _
from solo.models import SingletonModel


class SideBar(SingletonModel):
    class Meta:
        verbose_name = _('Sidebar')
        verbose_name_plural = _('Sidebars')

    widgets = models.ManyToManyField('Widget', through='SideBarToWidget')

    def __str__(self):
        return ugettext('Sidebar')

SideBarToWidget

Негізінде, біз бұл үлгіні виджеттердің ретін орнатуға мүмкіндік беретін жалғыз сан өрісі үшін нақты орнаттық.
Бұл үлгіні нақты көрсетпесеңіз, ManyToMany қатынас нысандарының сыртқы кілттері бар кесте жасалады.

class SideBarToWidget(models.Model):
    sidebar = models.ForeignKey(SideBar, verbose_name=_('Sidebar'), on_delete=models.CASCADE)
    widget = models.ForeignKey(Widget, verbose_name=_('Widget'), on_delete=models.CASCADE)

    number = models.PositiveIntegerField(verbose_name=_('Ordering'))

    def __str__(self):
        return self.widget.__str__()

    class Meta:
        ordering = ('number',)

Виджет

Ал енді полиморфты сипаты бар ең қызықты модель. Бұл модель оның әрекетін жасалған кезде таңдалған түрі бойынша анықтауымен ерекшеленеді.
Виджет түрі виджет_түрі өрісі арқылы анықталады.

widget_type = models.IntegerField(
    verbose_name=_('Widget type'),
    choices=WIDGET_TYPE_CHOICES,
    default=HTML
)

Өріс түрін таңдау келесі сынып айнымалылары арқылы конфигурацияланады

HTML = 1
STANDARD = 2
WIDGET_TYPE_CHOICES = (
    (HTML, _('HTML Widget')),
    (STANDARD, _('Standard Widget')),
)

WIDGET_TYPE_TO_IMPL = {
    HTML: 'htmlwidgetimpl',
    STANDARD: 'standardwidgetimpl'
}

Осы түрдің арқасында мен нақты іске асыруды алу үшін қай кестеге сілтеме жасау керектігін нақты анықтаймын.
Сонымен қатар, мен қарым-қатынас атауын қалыптастырудың белгілі бір ережелерін ұстанамын.
Мұны осы айнымалыда дұрыс байқауға болады.

WIDGET_TYPE_TO_IMPL = {
    HTML: 'htmlwidgetimpl',
    STANDARD: 'standardwidgetimpl'
}

Бұл сөздікте виджет түрі белгілі бір іске кіруге жауапты сипаттың атына сәйкес келеді.

  • htmlwidgetimpl -> HTMLWidgetImpl
  • standardwidgetimpl -> StandardWidgetImpl

Виджеттің сипаттамасы, яғни str әдісі келесідей құрылады.

def __str__(self):
    return self.description or str(dict(self.WIDGET_TYPE_CHOICES)[self.widget_type])

Көріп отырғаныңыздай, мен ыңғайлы болу үшін қосылған виджет үшін берілген сипаттаманы немесе WIDGET_TYPE_CHOICES сынып айнымалысынан * виджет_түрінің ағымдағы мәні арқылы алатын виджет түрінің сипаттамасын пайдаланамын. *

Бірақ ең қызықтысы виджетті көрсетуге жауап беретін render() әдісі.

def render(self):
    impl = getattr(self, self.WIDGET_TYPE_TO_IMPL[self.widget_type])
    return impl.render() if impl else ''

Көрсету әдісі метабағдарламалауды әлдеқашан қолданады, себебі ағымдағы виджет түрімен мен сипат атауы бойынша нақты іске асыруға сілтеме жасаймын.
Бұл үшін Python функциясы getattr пайдаланылады. Ал егер нақты іске асыру бар болса, сіз ешқашан білмейсіз, бірдеңе дұрыс болмады, мен нақты іске асырудың render() әдісін шақырамын.

Толық код

class Widget(models.Model):
    HTML = 1
    STANDARD = 2
    WIDGET_TYPE_CHOICES = (
        (HTML, _('HTML Widget')),
        (STANDARD, _('Standard Widget')),
    )

    WIDGET_TYPE_TO_IMPL = {
        HTML: 'htmlwidgetimpl',
        STANDARD: 'standardwidgetimpl'
    }

    class Meta:
        verbose_name = _('Widget')
        verbose_name_plural = _('Widgets')

    widget_type = models.IntegerField(
        verbose_name=_('Widget type'),
        choices=WIDGET_TYPE_CHOICES,
        default=HTML
    )

    description = models.CharField(verbose_name=_('Description'), max_length=255, null=True, blank=True)

    def __str__(self):
        return self.description or str(dict(self.WIDGET_TYPE_CHOICES)[self.widget_type])

    def render(self):
        impl = getattr(self, self.WIDGET_TYPE_TO_IMPL[self.widget_type])
        return impl.render() if impl else ''

AbstractWidgetImpl

Кодтың кейбір бөліктерін жалпылау үшін мен нақты іске асыру үшін дерексіз үлгіні қолданамын.

class AbstractWidgetImpl(models.Model):
    class Meta:
        abstract = True

    parent = models.OneToOneField(Widget, verbose_name=_('Widget'), on_delete=models.CASCADE, related_name='%(class)s',
                                  related_query_name='%(class)s')

Осы үлгінің көмегімен виджетпен нақты іске асырудың OneToOne қатынасын қалыптастырамын.
Мұнда ең қызықтысы келесі екі нәрсе

  • қатысты_атауы='%(сынып)s'
  • related_query_name='%(class)s'

Шындығында, мен бірнеше мақалада мұндай мақалаларды аз көрдім, бірақ дерексіз модельді жазғанда, % (сынып) s жазбасы модель атауын келесі принцип бойынша түрлендіреді.

  • HTMLWidgetImpl -> htmlwidgetimpl

Осылайша, мен іске асыруға тән қасиеттер үшін атау конвенцияларын анықтаймын.

HTMLWidgetImpl

Енді біз нақты енгізулерге жеттік. Мысалы, HTMLWidgetImpl мазмұн өрісі бар және HTMLWidgetImpl оның evileg_widgets/htmlwidgetimpl.html көрсету үлгісі туралы біледі.

Сондықтан, бізде render() әдісін өзіміздің іске асыруымыз бар.

class HTMLWidgetImpl(AbstractWidgetImpl):
    class Meta:
        verbose_name = _('HTML Widget')
        verbose_name_plural = _('HTML Widgets')

    content = models.TextField(verbose_name=_('HTML'))

    def render(self):
        return render_to_string(
            template_name='evileg_widgets/htmlwidgetimpl.html',
            context={'content': self.content}
        )

StandardWidgetImpl

Сол сияқты, біз StandardWidgetImpl үшін арнайы енгізуді ұсындық.

class StandardWidgetImpl(AbstractWidgetImpl):
    class Meta:
        verbose_name = _('Standard Widget')
        verbose_name_plural = _('Standard Widgets')

    title = models.CharField(verbose_name=_('Title'), max_length=255)
    content = models.TextField(verbose_name=_('HTML'))

    def render(self):
        return render_to_string(
            template_name='evileg_widgets/standardwidgetimpl.html',
            context={
                'title': self.title,
                'content': self.content
            }
        )

admin.py

Енді әкімші панелін және оның конфигурациялану жолын қарастырайық. Мұнда код қазірдің өзінде біршама қарапайым болады, сондықтан мен оны бірден беремін, содан кейін оның не істейтінін сипаттаймын.

# -*- coding: utf-8 -*-

from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from solo.admin import SingletonModelAdmin

from evileg_widgets.models import (
    SideBar,
    Widget,
    SideBarToWidget,
    HTMLWidgetImpl,
    StandardWidgetImpl
)


class AbstractWidgetImplInline(admin.StackedInline):
    verbose_name_plural = _('Configuration')
    can_delete = False


class HTMLWidgetImplInline(AbstractWidgetImplInline):
    model = HTMLWidgetImpl


class StandardWidgetImplInline(AbstractWidgetImplInline):
    model = StandardWidgetImpl


WIDGET_TYPE_TO_INLINE = {
    Widget.HTML: HTMLWidgetImplInline,
    Widget.STANDARD: StandardWidgetImplInline
}


class WidgetAdmin(admin.ModelAdmin):
    fields = ['widget_type', 'description', 'preview']
    readonly_fields = ['preview']

    def preview(self, obj):
        return obj.render()

    preview.short_description = _('Preview')

    def get_fields(self, request, obj=None):
        if obj is not None:
            return super().get_fields(request, obj)
        return ['widget_type']

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = super().get_readonly_fields(request, obj)
        if obj is not None:
            readonly_fields.append('widget_type')
        return readonly_fields

    def get_inline_instances(self, request, obj=None):
        if obj is not None:
            return [WIDGET_TYPE_TO_INLINE[obj.widget_type](self.model, self.admin_site)]
        return []


class SideBarToWidgetInline(admin.TabularInline):
    model = SideBarToWidget
    fields = ['number', 'widget']
    verbose_name_plural = _('Widgets')
    extra = 1


class SideBarAdmin(SingletonModelAdmin):
    inlines = [SideBarToWidgetInline]


admin.site.register(SideBar, SideBarAdmin)
admin.site.register(Widget, WidgetAdmin)

Кезекте

Біріншіден, әрбір нақты іске асыру үшін кірістірілген пішіндерді жасаймын, сонымен қатар оларды виджет түрлеріне байланыстырамын.

class AbstractWidgetImplInline(admin.StackedInline):
    verbose_name_plural = _('Configuration')
    can_delete = False


class HTMLWidgetImplInline(AbstractWidgetImplInline):
    model = HTMLWidgetImpl


class StandardWidgetImplInline(AbstractWidgetImplInline):
    model = StandardWidgetImpl


WIDGET_TYPE_TO_INLINE = {
    Widget.HTML: HTMLWidgetImplInline,
    Widget.STANDARD: StandardWidgetImplInline
}

Виджет әкімшісі

Содан кейін виджет пішінін көрсетудің екі кезеңін анықтайтын виджет пішінінің әкімшілігін қалыптастырамын.

Виджетті жасау кезінде оның түрін ғана орнатуға рұқсат етемін

Содан кейін, түріне байланысты мен нақты сипаттарды өңдеуге рұқсат беремін. Айтпақшы, бұл виджеттің алдын ала қарауы да көрсетіледі. Алдын ала қарау үшін өзіңіздің CSS-ді қосуыңыз керек, бірақ бұл мақалаға қолданылмайды. Сондай-ақ скриншот көптілділікті көрсетеді, бірақ бұл мақалаға қатысты емес, тек көптілділік үшін django-modeltranslation пайдалану керек екенін айта аламын.

Көрсетілген өрістерді таңдау және конфигурациялау

Виджет жасалғанына байланысты көрсетілетін өрістерді таңдау get_fields әдісін қайта анықтау арқылы орындалады.

def get_fields(self, request, obj=None):
    if obj is not None:
        return super().get_fields(request, obj)
    return ['widget_type']

тек оқу_өрістері үшін де солай. Виджет нысаны дерекқорда бұрыннан бар болғанда, виджет_түрі өрісін өңдеу мүмкіндігін арнайы шектеймін. Әйтпесе, ол виджетті басқару кодының логикасын қиындатуы мүмкін. Мен оны қаламаймын.

def get_readonly_fields(self, request, obj=None):
    readonly_fields = super().get_readonly_fields(request, obj)
    if obj is not None:
        readonly_fields.append('widget_type')
    return readonly_fields

Кірістірілген пішінді орнатыңыз

Бірақ ең бастысы таңдалған виджет түріне байланысты Inline пішінін таңдау. Кодтан көріп отырғаныңыздай, нақты іске асырудың қажетті түріне сәйкес келетін кірістірілген объектілердің алдын ала дайындалған сөздігін пайдаланып пішінді таңдаймын.

def get_inline_instances(self, request, obj=None):
    if obj is not None:
        return [WIDGET_TYPE_TO_INLINE[obj.widget_type](self.model, self.admin_site)]
    return []

Бүйірлік тақта

Ең қарапайымы - бүйірлік тақта үлгілерінің дисплейі

class SideBarToWidgetInline(admin.TabularInline):
    model = SideBarToWidget
    fields = ['number', 'widget']
    verbose_name_plural = _('Widgets')
    extra = 1


class SideBarAdmin(SingletonModelAdmin):
    inlines = [SideBarToWidgetInline]

Әкімшілік үлгіде тіркеу

Әкімшілік панельде үлгілерді тіркеу ғана қалады. Мен нақты енгізулерді бөлек тіркемейтінімді ескеріңіз. Менің ойымша, мұндай ақпарат тікелей емес, тек кірістірілген формалар арқылы қол жетімді болуы керек.

admin.site.register(SideBar, SideBarAdmin)
admin.site.register(Widget, WidgetAdmin)

Үлгілер

Менің жағдайымда нақты іске асыру үлгілері осылай көрінеді

htmlwidgetimpl.html

{{ content|safe }}

standardwidgetimpl.html

<div class="card box-shadow m-2">
  <h5 class="card-header text-center">{{ title }}</h5>
  <div class="card-body">
    {{ content|safe }}
  </div>
</div>

Үлгі тегтері

Соңында, шаблон тегін пайдаланып бүйірлік тақтаны қалай көрсету керектігін көрсетемін.

templatetags/evileg_widgets.py

# -*- coding: utf-8 -*-

from django import template
from evileg_widgets.models import SideBar

register = template.Library()


@register.inclusion_tag('evileg_widgets/sidebar.html')
def sidebar():
    return {'widgets': SideBar.get_solo().widgets.order_by('sidebartowidget__number')}

evilge_widgets/sidebar.html

{% for widget in widgets %}
  {{ widget.render }}
{% endfor %}

Бүйірлік тақтаны көрсету

{% load sidebar from evileg_widgets %}
{% sidebar %}

Қорытынды

  • Бұл тәсіл динамикалық виджеттер үшін ғана пайдалы емес, сонымен қатар жалпы бірқатар тапсырмалар үшін өте жақсы архитектуралық шешім бола алады.
  • Бұл тәсілдің артықшылығы мынада: сіз сайт үлгісінің кодын айтарлықтай жеңілдете аласыз, сондай-ақ адаптер ретінде әрекет ететін полиморфты модельдің артында нақты іске асыруды жасыра аласыз.
  • Нысандардың одан әрі түрлерін іске асыру үшін жаңа іске асыру үлгісін қосу және әкімшілік үлгідегі барлық қажетті айнымалыларды жаңарту, кейбір жағдайларда фронтендік пішіндерді қосу жеткілікті болатыны да өте маңызды.
  • Бірақ ең бастысы, бұл шын мәнінде жобаның архитектурасын қалыптастыратын шешім. Архитектура одан әрі кеңейту қазірдің өзінде азды-көпті стандартталғанын білдіреді және бірнеше бағдарламашылар жобада жұмыс істеп жатқанда, жаңадан келгендерге жаңа нәрсе жазу талап етілмейді. Оларға бұл архитектураның қалай жұмыс істейтінін түсіну жеткілікті болады, ал жаңа типтерді енгізу олар үшін қарапайым әдетке айналады.
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:50ұпай,
  • Бағалау ұпайлары-4
m
  • molni99
  • Қаз. 25, 2024, 10:37 Т.Қ.

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:80ұпай,
  • Бағалау ұпайлары4
m
  • molni99
  • Қаз. 25, 2024, 10:29 Т.Қ.

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:20ұпай,
  • Бағалау ұпайлары-10
Соңғы пікірлер
ИМ
Игорь МаксимовҚар. 22, 2024, 8:51 Т.Ж.
Django - Оқулық 017. Теңшелген Django кіру беті Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiҚаз. 31, 2024, 11:37 Т.Ж.
Django - Сабақ 064. Python Markdown кеңейтімін қалай жазуға болады Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEҚаз. 19, 2024, 5:19 Т.Ж.
Qt Creator көмегімен fb3 файл оқу құралы Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовҚаз. 5, 2024, 4:51 Т.Ж.
Django - Сабақ 064. Python Markdown кеңейтімін қалай жазуға болады Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5Шілде 5, 2024, 8:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Енді форумда талқылаңыз
m
moogoҚар. 22, 2024, 4:17 Т.Ж.
Mosquito Spray System Effective Mosquito Systems for Backyard | Eco-Friendly Misting Control Device & Repellent Spray - Moogo ; Upgrade your backyard with our mosquito-repellent device! Our misters conce…
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 12:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Қар. 15, 2024, 3:04 Т.Ж.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectМаусым 4, 2022, 12:49 Т.Ж.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…

Бізді әлеуметтік желілерде бақылаңыз