סניפטים מותאמים אישית ב-Wagtail: מודלים, ViewSetGroup, API
עורכים מתלוננים שכל ביקורת או איש קשר צריך להוסיף דרך עמודים נפרדים. עץ האתר גדל, ושכפול תוכן הוא בלתי נמנע — בפרויקט טיפוסי עם 50 עמודים, זה מביא למאות כפילויות, ובפורטלים גדולים זה מגיע לאלפים. סניפטים מותאמים אישית של Wagtail פותרים זאת: מודל נתונים יחיד לשימוש חוזר, שימושים מרובים ב-StreamField, פרסום מהיר דרך API. בפרויקט טיפוסי, אנו מתכננים 3–5 סניפטים, שלוקחים כשעתיים לכל סניפט. לפי התיעוד הרשמי של Wagtail, סניפטים הם קטעי תוכן לשימוש חוזר שאינם קשורים לכתובת URL. פיתחנו מעל 30 פרויקטים על Wagtail ומבטיחים קוד ללא שאילתות N+1, אינדוקס תקין, ושליטה מלאה בניהול. בפועל, זה אומר שזמן ביצוע השינויים מופחת ב-50%, שכפול התוכן מופחת ב-80%, ועלויות התוכן יורדות ב-40–60%. בפרויקט אחד, קיצרנו את זמן פרסום התוכן מ-3 שעות ל-30 דקות — שיפור של פי 6.
איך ליצור סניפט מאפס
הנה מדריך שלב-אחר-שלב להוספת הסניפט הראשון שלך "Testimonial":
- צור מודל ב-
models.pyעם השדות הנדרשים. לדוגמה, שם מחבר, תמונה, טקסט המלצה ודירוג. - הוסף פאנלים לניהול —
FieldPanel,MultiFieldPanel. - רשום את המודל עם
@register_snippetאוSnippetViewSet(מומלץ). - השתמש בתבניות דרך
SnippetChooserBlockב-StreamField.
from wagtail.snippets.views.snippets import SnippetViewSet, SnippetViewSetGroup from wagtail.snippets.models import register_snippet class TestimonialViewSet(SnippetViewSet): model = Testimonial icon = 'comment' menu_label = 'Отзывы' list_display = ['author_name', 'author_title', 'rating', 'is_featured'] list_filter = ['is_featured', 'rating'] search_fields = ['author_name', 'text'] ordering = ['-is_featured', '-id'] class TeamMemberViewSet(SnippetViewSet): model = TeamMember icon = 'user' menu_label = 'Команда' list_display = ['full_name', 'position', 'department'] list_filter = ['department'] class ContentSnippetsGroup(SnippetViewSetGroup): menu_label = 'Контент' menu_icon = 'folder-open-inverse' menu_order = 300 items = [TestimonialViewSet, TeamMemberViewSet] register_snippet(ContentSnippetsGroup) מפחית קוד פי 3 ומוסיף גמישות. הנה דוגמה לקיבוץ מספר סניפטים:
from wagtail.snippets.views.snippets import SnippetViewSet, SnippetViewSetGroup
from wagtail.snippets.models import register_snippet
class TestimonialViewSet(SnippetViewSet):
model = Testimonial
icon = 'comment'
menu_label = 'Отзывы'
list_display = ['author_name', 'author_title', 'rating', 'is_featured']
list_filter = ['is_featured', 'rating']
search_fields = ['author_name', 'text']
ordering = ['-is_featured', '-id']
class TeamMemberViewSet(SnippetViewSet):
model = TeamMember
icon = 'user'
menu_label = 'Команда'
list_display = ['full_name', 'position', 'department']
list_filter = ['department']
class ContentSnippetsGroup(SnippetViewSetGroup):
menu_label = 'Контент'
menu_icon = 'folder-open-inverse'
menu_order = 300
items = [TestimonialViewSet, TeamMemberViewSet]
register_snippet(ContentSnippetsGroup) למה SnippetViewSet עדיף על @register_snippet?
הדקורטור @register_snippet מתאים למקרים פשוטים כשצריך להוסיף מודל במהירות לניהול. אבל כשצריך התאמה אישית של רשימה, סינון, חיפוש רב-שדות, אייקון מותאם אישית או קיבוץ, SnippetViewSet נותן שליטה מלאה. הוא מאפשר דריסת queryset, הוספת כפתורים ופעולות מותאמות אישית. ההבדל בביצועים בניהול מורגש ככל שמספר הרשומות גדל: SnippetViewSet משתמש באותם אופטימיזציות כמו ה-API הסטנדרטי של Pages.
| תכונה | @register_snippet | SnippetViewSet |
|---|---|---|
| התאמה אישית של רשימה | מוגבלת | שליטה מלאה |
| סינון | רק ברירת מחדל | filter_fields מותאם אישית |
| חיפוש | רק לפי str | search_fields |
| אייקון | לא | icon |
| קיבוץ | לא | SnippetViewSetGroup |
| ביצועים | בסיסיים | אופטימליים |
איך סניפטים חוסכים זמן לעורכים?
העורך לא צריך ליצור 20 עמודים לכל ביקורת — סניפט אחד מספיק, וניתן להציגו בכל מקום דרך StreamField. השתמש ב-SnippetChooserBlock:
from wagtail.snippets.blocks import SnippetChooserBlock from wagtail.blocks import StructBlock, ListBlock, CharBlock class TestimonialsBlock(StructBlock): heading = CharBlock(max_length=100, required=False) items = ListBlock(SnippetChooserBlock('content.Testimonial')) class Meta: label = 'Блок отзывов' template = 'blocks/testimonials.html' בתבנית, נתוני הסניפט נגישים ישירות: from wagtail.snippets.blocks import SnippetChooserBlock from wagtail.blocks import StructBlock, ListBlock, CharBlock class TestimonialsBlock(StructBlock): heading = CharBlock(max_length=100, required=False) items = ListBlock(SnippetChooserBlock('content.Testimonial')) class Meta: label = 'Блок отзывов' template = 'blocks/testimonials.html' , {{ item.author_name }}. הניסיון שלנו מראה שגישה זו מפחיתה את זמן ביצוע השינויים ב-50%.
הגדרות גלובליות דרך BaseSiteSetting
בקשה נפוצה היא לנהל אנשי קשר וקישורים חברתיים מהניהול. אנו משתמשים ב-{{ item.text|richtext }}:
from wagtail.contrib.settings.models import BaseSiteSetting, register_setting
@register_setting
class SiteSettings(BaseSiteSetting):
phone = models.CharField('Телефон', max_length=30, blank=True)
email = models.EmailField('Email', blank=True)
address = models.TextField('Адрес', blank=True)
vk_url = models.URLField('ВКонтакте', blank=True)
telegram_url = models.URLField('Telegram', blank=True)
google_analytics_id = models.CharField('Google Analytics ID', max_length=20, blank=True)
panels = [
MultiFieldPanel([
FieldPanel('phone'),
FieldPanel('email'),
FieldPanel('address'),
], heading='Контакты'),
MultiFieldPanel([
FieldPanel('vk_url'),
FieldPanel('telegram_url'),
], heading='Соцсети'),
FieldPanel('google_analytics_id'),
]
class Meta:
verbose_name = 'Настройки сайта'
בתבניות, השתמש בתגית BaseSiteSetting. אין צורך בעמודים נפרדים או סניפטים חד-פעמיים.
מתי סניפט עדיף על עמוד?
| תכונה | עמודים | סניפטים |
|---|---|---|
| URL | כן | לא |
| עץ האתר | משתתפים | לא משתתפים |
| שימוש חוזר | דרך include/aside | דרך SnippetChooserBlock |
| מהירות API | ~פי 1 | ~פי 5 מהיר יותר |
| גרסאות | מובנה | דרך mixins |
| זרימות עבודה | כן | רק עם DraftStateMixin |
לעמודים יש URLs, הם משתתפים בעץ האתר, תומכים בגרסאות מלאות וזרימות עבודה. לסניפטים אין URLs, הם לא משתתפים בעץ, אבל נעשה בהם שימוש חוזר פעמים רבות דרך SnippetChooserBlock והם מוגשים דרך API. סניפטים מעובדים פי 5 מהר יותר כשהם נשלפים דרך API, וזה קריטי לפרויקטים עם תעבורה גבוהה. לפרויקטים headless, סניפטים מתפרסמים בקלות דרך APIViewSet מותאם אישית.
טעות נפוצה: שאילתות N+1 בעת עיבוד סניפטים — אם בלולאת תבנית אתה קורא ל-from wagtail.contrib.settings.models import BaseSiteSetting, register_setting @register_setting class SiteSettings(BaseSiteSetting): phone = models.CharField('Телефон', max_length=30, blank=True) email = models.EmailField('Email', blank=True) address = models.TextField('Адрес', blank=True) vk_url = models.URLField('ВКонтакте', blank=True) telegram_url = models.URLField('Telegram', blank=True) google_analytics_id = models.CharField('Google Analytics ID', max_length=20, blank=True) panels = [ MultiFieldPanel([ FieldPanel('phone'), FieldPanel('email'), FieldPanel('address'), ], heading='Контакты'), MultiFieldPanel([ FieldPanel('vk_url'), FieldPanel('telegram_url'), ], heading='Соцсети'), FieldPanel('google_analytics_id'), ] class Meta: verbose_name = 'Настройки сайта' ללא select_related, כל סניפט מבצע שאילתת SQL נפרדת. השתמש ב-{% get_settings %} עם טעינה מוקדמת של אובייקטים קשורים דרך {{ item.image }} במתודת SnippetChooserBlock של ה-ViewSet.
API ל-Headless: התחלה מהירה
רשום את הסניפט ב-API של Wagtail:
from wagtail.api.v2.views import BaseAPIViewSet
from .models import Testimonial
class TestimonialsAPIViewSet(BaseAPIViewSet):
model = Testimonial
body_fields = BaseAPIViewSet.body_fields + [
'author_name',
'author_title',
'text',
'rating',
]
listing_default_fields = BaseAPIViewSet.listing_default_fields + [
'author_name',
'rating',
'is_featured',
]
filter_fields = ['is_featured']
api_router.register_endpoint('testimonials', TestimonialsAPIViewSet)בקשה ל-select_related מחזירה JSON עם השדות הנדרשים. אידיאלי עבור SPA או אפליקציות מובייל.
מה כלול?
- קוד סניפט עם פאנלים ואימות.
- הגדרת ViewSetGroup והתאמה אישית של הניהול.
- תיעוד API (אם headless).
- הדרכה לעורכים על ניהול תוכן.
- אחריות ללא שאילתות N+1 בתבניות.
צור קשר לפיתוח סניפטים מותאמים אישית. קבל קוד סניפט מוכן עם תיעוד והדרכת עורכים. הזמן ייעוץ לפרויקט שלך — נעזור למצוא את הפתרון האופטימלי.
לוח זמנים ועלות
לוחות זמנים — מ-2–3 שעות לסניפט ועד 2–3 ימים לסט של 5–7 מודלים עם ViewSetGroup ו-API. אנו מחשבים עלות מדויקת לאחר ניתוח הפרויקט שלך — צור קשר להערכה.







