מודלים מותאמים אישית של Wagtail Page: שדות, פאנלים, ניתובים

כאשר היכולות הסטנדרטיות של Wagtail אינן מספיקות לעמודים לא שגרתיים, אנו מפתחים מודלי Page מותאמים אישית. הצוות שלנו בונה שדות, פאנלים ו-routes המותאמים למשימות שלך, ומספק פרויקט מפתח לתמיכה. כתוצאה מכך, אתה מקבל פאנל ניהול ידידותי למשתמש ופתרון גמיש שגדל עם העסק שלך.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
מודלים מותאמים אישית של Wagtail Page: שדות, פאנלים, ניתובים
בינוני
~2-3 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1307
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

מצב טיפוסי: לקוח מבקש לא רק עמוד "אודותינו", אלא קטלוג שירותים מלא עם מחירים, תעריפים וסינון. בלוקים מוכנים לא מתאימים — הם צריכים שדות מותאמים אישית, טאבים בניהול וניתוב משלהם. הפתרון הוא מודלי Page מותאמים אישית. השתמשנו בגישה זו בכל פרויקט Wagtail במשך למעלה מ-5 שנים וצברנו יותר מ-30 מקרי הצלחה. לדוגמה, חנות מקוונת דרשה 7 סוגי עמודים שונים עם שדות ויחסים ייחודיים — הפיתוח כולו ארך 4 ימים, והעורכים קיבלו ממשק נקי עם טאבים. בממוצע, פרויקט מורכב דורש 5–10 סוגי עמודים. שדות Wagtail הסטנדרטיים (כותרת, slug, גוף) אינם מכסים את הדרישות: אם דוחסים תעריפים, מפרטים וביקורות לתוך RichTextField אחד, הניהול הופך לסיוט. בפרויקט אחד, עורכים בילו 20 דקות בעריכת עמוד בודד — לאחר יישום מודלים מותאמים אישית, הזמן ירד ל-2 דקות. הפחתה של 90%.

כיצד מודלי Page מותאמים אישית מקלים על חיי העורך?

עורכים זקוקים לממשק ניהול מובן. לשם כך, אנו משתמשים ב-TabbedInterface עם אובייקטים של ObjectList. הדוגמה שלהלן מציגה מודל ServicePage עם שלושה טאבים. שימו לב ל-InlinePanel עבור רשומות קשורות ול-pricing_panels להצגת מחירים.

from django.db import models
from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField, StreamField
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel, TabbedInterface, ObjectList
from wagtail.search import index
from modelcluster.fields import ParentalKey

class ServicePage(Page):
    intro = models.CharField(max_length=500, blank=True)
    body = RichTextField(blank=True)
    icon = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    price_from = models.DecimalField(max_digits=10, decimal_places=0, null=True, blank=True)

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
        index.FilterField('price_from'),
    ]

    content_panels = Page.content_panels + [
        FieldPanel('intro'),
        FieldPanel('icon'),
        FieldPanel('body'),
        InlinePanel('features', label='Особенности'),
    ]

    promote_panels = [
        MultiFieldPanel([
            FieldPanel('slug'),
            FieldPanel('seo_title'),
            FieldPanel('search_description'),
        ], heading='SEO'),
    ]

    pricing_panels = [
        FieldPanel('price_from'),
        InlinePanel('pricing_tiers', label='Тарифы'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Содержание'),
        ObjectList(pricing_panels, heading='Цены'),
        ObjectList(promote_panels, heading='SEO'),
    ])

    parent_page_types = ['services.ServicesIndexPage']
    subpage_types = []

    class Meta:
        verbose_name = 'Service Page'

class ServiceFeature(Orderable):
    page = ParentalKey(ServicePage, on_delete=models.CASCADE, related_name='features')
    title = models.CharField(max_length=255)
    text = models.TextField(blank=True)
    icon = models.CharField(max_length=50, blank=True)

    panels = [
        FieldPanel('title'),
        FieldPanel('text'),
        FieldPanel('icon'),
    ]

למה RoutablePage עדיף על עמודים נפרדים?

אם אתם צריכים בלוג עם קטגוריות ותגיות, ניתן ליצור מודלי Page נפרדים לכל ישות, או להשתמש ב-from django.db import models from wagtail.models import Page, Orderable from wagtail.fields import RichTextField, StreamField from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel, TabbedInterface, ObjectList from wagtail.search import index from modelcluster.fields import ParentalKey class ServicePage(Page): intro = models.CharField(max_length=500, blank=True) body = RichTextField(blank=True) icon = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) price_from = models.DecimalField(max_digits=10, decimal_places=0, null=True, blank=True) search_fields = Page.search_fields + [ index.SearchField('intro'), index.SearchField('body'), index.FilterField('price_from'), ] content_panels = Page.content_panels + [ FieldPanel('intro'), FieldPanel('icon'), FieldPanel('body'), InlinePanel('features', label='Особенности'), ] promote_panels = [ MultiFieldPanel([ FieldPanel('slug'), FieldPanel('seo_title'), FieldPanel('search_description'), ], heading='SEO'), ] pricing_panels = [ FieldPanel('price_from'), InlinePanel('pricing_tiers', label='Тарифы'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Содержание'), ObjectList(pricing_panels, heading='Цены'), ObjectList(promote_panels, heading='SEO'), ]) parent_page_types = ['services.ServicesIndexPage'] subpage_types = [] class Meta: verbose_name = 'Service Page' class ServiceFeature(Orderable): page = ParentalKey(ServicePage, on_delete=models.CASCADE, related_name='features') title = models.CharField(max_length=255) text = models.TextField(blank=True) icon = models.CharField(max_length=50, blank=True) panels = [ FieldPanel('title'), FieldPanel('text'), FieldPanel('icon'), ] . השוו: עם 10 קטגוריות ו-20 תגיות, עמודים נפרדים ידרשו 30 מודלים. RoutablePage אחד עם דקורטורים של BlogIndexPage מחליף את כולם, חותך את הקוד בחצי ומפשט את התחזוקה. לפי תיעוד Wagtail, RoutablePage מאפשר לעמוד אחד לטפל במספר תצוגות URL.

from wagtail.contrib.routable_page.models import RoutablePage, path, re_path

class BlogIndexPage(RoutablePage, Page):
    intro = RichTextField(blank=True)

    content_panels = Page.content_panels + [FieldPanel('intro')]
    subpage_types = ['blog.BlogPost']

    @path('')
    def index_view(self, request):
        posts = BlogPost.objects.live().child_of(self).order_by('-first_published_at')
        from django.core.paginator import Paginator
        paginator = Paginator(posts, 12)
        return self.render(request, context_overrides={
            'posts': paginator.get_page(request.GET.get('page')),
        })

    @path('category/<str:category_slug>/')
    def category_view(self, request, category_slug):
        category = get_object_or_404(BlogCategory, slug=category_slug)
        posts = BlogPost.objects.live().child_of(self).filter(categories=category)
        return self.render(request, context_overrides={
            'posts': posts,
            'category': category,
        }, template='blog/blog_category.html')

    @path('tag/<str:tag>/')
    def tag_view(self, request, tag):
        posts = BlogPost.objects.live().child_of(self).filter(tags__slug=tag)
        return self.render(request, context_overrides={
            'posts': posts,
            'tag': tag,
        })

    @re_path(r'^archive/(\d{4})/$')
    def year_archive(self, request, year):
        posts = BlogPost.objects.live().child_of(self).filter(date__year=year)
        return self.render(request, context_overrides={
            'posts': posts,
            'year': year,
        })

כיצד להתאים אישית תמונות?

לצורך עמידה בדרישות SEO ונגישות, אנו מרחיבים את מודל התמונה הסטנדרטי, ומוסיפים שדות @path ו-from wagtail.contrib.routable_page.models import RoutablePage, path, re_path class BlogIndexPage(RoutablePage, Page): intro = RichTextField(blank=True) content_panels = Page.content_panels + [FieldPanel('intro')] subpage_types = ['blog.BlogPost'] @path('') def index_view(self, request): posts = BlogPost.objects.live().child_of(self).order_by('-first_published_at') from django.core.paginator import Paginator paginator = Paginator(posts, 12) return self.render(request, context_overrides={ 'posts': paginator.get_page(request.GET.get('page')), }) @path('category/<str:category_slug>/') def category_view(self, request, category_slug): category = get_object_or_404(BlogCategory, slug=category_slug) posts = BlogPost.objects.live().child_of(self).filter(categories=category) return self.render(request, context_overrides={ 'posts': posts, 'category': category, }, template='blog/blog_category.html') @path('tag/<str:tag>/') def tag_view(self, request, tag): posts = BlogPost.objects.live().child_of(self).filter(tags__slug=tag) return self.render(request, context_overrides={ 'posts': posts, 'tag': tag, }) @re_path(r'^archive/(\d{4})/$') def year_archive(self, request, year): posts = BlogPost.objects.live().child_of(self).filter(date__year=year) return self.render(request, context_overrides={ 'posts': posts, 'year': year, }) . זה מאפשר לעורכים למלא טקסט חלופי חובה ולציין את מחבר התמונה.

# core/models.py from wagtail.images.models import AbstractImage, AbstractRendition, Image

class CustomImage(AbstractImage):
    alt_text = models.CharField(max_length=255, blank=True)
    credit = models.CharField(max_length=255, blank=True)

    admin_form_fields = Image.admin_form_fields + [
        'alt_text',
        'credit',
    ]

    @property
    def default_alt_text(self):
        return self.alt_text or self.title

class CustomRendition(AbstractRendition):
    image = models.ForeignKey(CustomImage, on_delete=models.CASCADE, related_name='renditions')

    class Meta:
        unique_together = [('image', 'filter_spec', 'focal_point_key')]

get_context — העברת נתונים נוספים

המתודה alt_text מאפשרת להעשיר את התבנית בנתונים מטבלאות אחרות, פרמטרי GET או טפסים. היא נקראת בעת רינדור העמוד.

def get_context(self, request):
    context = super().get_context(request)
    context['related_posts'] = BlogPost.objects.live().exclude(
        pk=self.pk
    ).filter(
        categories__in=self.categories.all()
    ).distinct().order_by('-first_published_at')[:3]
    context['comment_form'] = CommentForm()
    context['current_tag'] = request.GET.get('tag')
    return context

השוואת גישות לבניית מודלי Page

גישה שימוש מורכבות מהירות תחזוקה
שדות Page בסיסיים עמודים פשוטים (אודות, צור קשר) נמוכה גבוהה
שדות מותאמים אישית + פאנלים עמודי שירות, קטלוגים בינונית בינונית
StreamField עמודי נחיתה גמישים, עריכה בינונית נמוכה
RoutablePage בלוגים, קטעים מסוננים גבוהה גבוהה
תמונות מותאמות אישית אתרי מדיה, ארכיוני תמונות נמוכה גבוהה
סוג שדה דוגמת שימוש לוח ניהול
credit כותרת, תיאור קצר # core/models.py from wagtail.images.models import AbstractImage, AbstractRendition, Image class CustomImage(AbstractImage): alt_text = models.CharField(max_length=255, blank=True) credit = models.CharField(max_length=255, blank=True) admin_form_fields = Image.admin_form_fields + [ 'alt_text', 'credit', ] @property def default_alt_text(self): return self.alt_text or self.title class CustomRendition(AbstractRendition): image = models.ForeignKey(CustomImage, on_delete=models.CASCADE, related_name='renditions') class Meta: unique_together = [('image', 'filter_spec', 'focal_point_key')]
get_context תוכן ראשי def get_context(self, request): context = super().get_context(request) context['related_posts'] = BlogPost.objects.live().exclude( pk=self.pk ).filter( categories__in=self.categories.all() ).distinct().order_by('-first_published_at')[:3] context['comment_form'] = CommentForm() context['current_tag'] = request.GET.get('tag') return context
CharField בלוקים גמישים (טקסט, תמונות, וידאו) FieldPanel
RichTextField קישור לתמונה או קטגוריה FieldPanel עם בורר
StreamField רשומות קשורות (תעריפים, מאפיינים) StreamFieldPanel

בפרויקט אמיתי לחנות מקוונת עם קטלוג של 10,000 מוצרים, פיתחנו 7 סוגי עמודים: CatalogPage, ProductPage, BrandPage, PromotionPage, ReviewPage, FAQPage ו-ContactPage. עבור ProductPage השתמשנו ב-StreamField עם בלוקי תיאור, טבלאות מפרט וקרוסלות תמונות. CatalogPage נבנה על RoutablePage עם סינון לפי קטגוריות, מחיר ותגיות. הפיתוח ארך 4 ימים, העורכים הוכשרו בשעה. זמן פרסום מוצר חדש ירד פי 10.

תהליך העבודה על מודלי Page מותאמים אישית

  1. ניתוח — אנו לומדים את מבנה התוכן של הלקוח, קובעים שדות ויחסים נחוצים.
  2. עיצוב — אנו מציירים דיאגרמת ER של המודלים, מתכננים את ממשק העורך (טאבים, פאנלים מובנים).
  3. יישום — אנו כותבים מודלים של Django, מגדירים ForeignKey, FieldPanel ו-InlinePanel.
  4. בדיקות — אנו בודקים גרסאות, טיוטות, זרימת פרסום וביצועים.
  5. פריסה — אנו משחררים לסביבת ייצור, מגדירים אינדוקס לחיפוש.

מה כלול בתוצאה

  • קוד מקור של מודלי Page מותאמים אישית עם תיעוד בעברית.
  • לוח ניהול עם טאבים, אימות שדות והשלמה אוטומטית.
  • אינדוקס מוגדר לחיפוש באתר.
  • תמיכה בגרסאות ו-Workflow לאישור פרסום.
  • הכשרת עורכים (שעה מקוונת) והוראות כתובות.

לוח זמנים ואחריות

פיתוח של 5–7 מודלי Page עם RoutablePage ו-InlinePanel אורך 3–6 ימים. העלות מחושבת באופן אישי לפי מורכבות. אנו נותנים אחריות ל-6 חודשים על כל המודלים: כל תקלות מתוקנות בחינם. גישה זו חוסכת עד 40% מהתקציב בהשוואה ליצירת עמודים נפרדים.

נעריך את הפרויקט שלכם ביום אחד — פשוט צרו קשר. הניסיון שלנו: יותר מ-5 שנים עם Wagtail, יותר מ-30 פרויקטים מוצלחים. קבלו ייעוץ — נספר לכם כיצד מודלי Page מותאמים אישית יכולים לפתור את המשימה שלכם.