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 хостинг.

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

Пікірлер

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

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:66ұпай,
  • Бағалау ұпайлары-1
t

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:33ұпай,
  • Бағалау ұпайлары-10
t

Qt - Тест 001. Сигналы и слоты

  • Нәтиже:52ұпай,
  • Бағалау ұпайлары-4
Соңғы пікірлер
G
GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrАқп. 8, 2024, 6:43 Т.Қ.
Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCTМаусым 25, 2024, 1 Т.Ж.
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCTМамыр 5, 2024, 5:46 Т.Ж.
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

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