- 1. Тапсырмаларды орнату
- 2. үлгілер
- 3. admin.py
- 4. Үлгілер
- 5. Үлгі тегтері
- 6. Қорытынды
Бір күні мен сайтқа динамикалық виджеттерді қосу мәселесін шешіп, виджеттердің әртүрлі түрлерін қосу мүмкіндігімен, сондай-ақ нақты іске асыру үлгілерін қосу арқылы виджеттер жиынтығын одан әрі шектеусіз кеңейту мүмкіндігімен шештім.
Жүйе бір Виджет класы болатындай, ол жасалған кезде таңдалған түріне байланысты нақты іске асыру үлгісіне жатады, мысалы, кейбір арнайы параметрлерді қамтитын 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 %}
Қорытынды
- Бұл тәсіл динамикалық виджеттер үшін ғана пайдалы емес, сонымен қатар жалпы бірқатар тапсырмалар үшін өте жақсы архитектуралық шешім бола алады.
- Бұл тәсілдің артықшылығы мынада: сіз сайт үлгісінің кодын айтарлықтай жеңілдете аласыз, сондай-ақ адаптер ретінде әрекет ететін полиморфты модельдің артында нақты іске асыруды жасыра аласыз.
- Нысандардың одан әрі түрлерін іске асыру үшін жаңа іске асыру үлгісін қосу және әкімшілік үлгідегі барлық қажетті айнымалыларды жаңарту, кейбір жағдайларда фронтендік пішіндерді қосу жеткілікті болатыны да өте маңызды.
- Бірақ ең бастысы, бұл шын мәнінде жобаның архитектурасын қалыптастыратын шешім. Архитектура одан әрі кеңейту қазірдің өзінде азды-көпті стандартталғанын білдіреді және бірнеше бағдарламашылар жобада жұмыс істеп жатқанда, жаңадан келгендерге жаңа нәрсе жазу талап етілмейді. Оларға бұл архитектураның қалай жұмыс істейтінін түсіну жеткілікті болады, ал жаңа типтерді енгізу олар үшін қарапайым әдетке айналады.