@@ -146,6 +146,12 @@ class Meta:
146146 # ajax_template = ''
147147 # search_template = ''
148148
149+ # Template used in site search results.
150+ search_template = "coderedcms/pages/search_result.html"
151+
152+ # Template used for related pages, Latest Pages block, and Page Preview block.
153+ miniview_template = "coderedcms/pages/page.mini.html"
154+
149155 ###############
150156 # Content fields
151157 ###############
@@ -217,6 +223,41 @@ class Meta:
217223 help_text = _ ("Enable filtering child pages by these classifiers." ),
218224 )
219225
226+ #####################
227+ # Related Page Fields
228+ #####################
229+
230+ # Subclasses can override this to query on a specific page model, in the
231+ # format "appname.Model". By default sibling pages are used.
232+ related_query_pagemodel = None
233+
234+ # Subclasses can override this to enabled related pages by default.
235+ related_show_default = False
236+
237+ related_show = models .BooleanField (
238+ default = related_show_default ,
239+ verbose_name = _ ("Show list of related pages" ),
240+ )
241+
242+ related_num = models .PositiveIntegerField (
243+ default = 3 ,
244+ verbose_name = _ ("Number of related pages to show" ),
245+ )
246+
247+ related_classifier_term = models .ForeignKey (
248+ "coderedcms.ClassifierTerm" ,
249+ blank = True ,
250+ null = True ,
251+ on_delete = models .SET_NULL ,
252+ related_name = "+" ,
253+ verbose_name = _ ("Preferred related classifier term" ),
254+ help_text = _ (
255+ "When getting related pages, pages with this term will be "
256+ "weighted over other classifier terms. By default, pages with "
257+ "the greatest number of classifiers in common are ranked highest."
258+ ),
259+ )
260+
220261 ###############
221262 # Layout fields
222263 ###############
@@ -311,6 +352,14 @@ class Meta:
311352 ],
312353 heading = _ ("Show Child Pages" ),
313354 ),
355+ MultiFieldPanel (
356+ [
357+ FieldPanel ("related_show" ),
358+ FieldPanel ("related_num" ),
359+ FieldPanel ("related_classifier_term" ),
360+ ],
361+ heading = _ ("Related Pages" ),
362+ ),
314363 ]
315364
316365 promote_panels = SeoMixin .seo_meta_panels + SeoMixin .seo_struct_panels
@@ -339,6 +388,7 @@ def __init__(self, *args, **kwargs):
339388 if not self .id :
340389 self .index_order_by = self .index_order_by_default
341390 self .index_show_subpages = self .index_show_subpages_default
391+ self .related_show = self .related_show_default
342392
343393 @cached_classmethod
344394 def get_edit_handler (cls ):
@@ -455,6 +505,60 @@ def get_index_children(self):
455505
456506 return query
457507
508+ def get_related_pages (
509+ self , pagetype : str = None , num : int = None
510+ ) -> models .QuerySet :
511+ """
512+ Returns a queryset of sibling pages, or the model type
513+ defined by ``pagetype`` or ``self.related_query_pagemodel``,
514+ ordered by number of shared classifier terms.
515+
516+ :param str pagetype: The model type to query on. This should
517+ be a string in the format "appname.Model".
518+ Overrides ``self.related_query_pagemodel``
519+
520+ :param int num: The number of results to return.
521+ Overrides ``self.related_num``.
522+ """
523+
524+ if pagetype is None :
525+ pagetype = self .related_query_pagemodel
526+
527+ if num is None :
528+ num = self .related_num
529+
530+ # Get our related query model, and queryset.
531+ if pagetype :
532+ querymodel = resolve_model_string (pagetype , self ._meta .app_label )
533+ r_qs = querymodel .objects .all ().live ()
534+ else :
535+ r_qs = self .get_parent ().specific .get_index_children ()
536+
537+ # Exclude self to avoid infinite recursion.
538+ r_qs = r_qs .exclude (pk = self .pk )
539+
540+ order_by = []
541+
542+ # If we have a preferred classifier term, order by that.
543+ if self .related_classifier_term :
544+ p_ct_q = models .Q (classifier_terms = self .related_classifier_term )
545+ r_qs = r_qs .annotate (p_ct = p_ct_q )
546+ order_by .append ("-p_ct" )
547+
548+ # If this page has a classifier, then order by number of
549+ # shared classifier terms.
550+ if self .classifier_terms .exists ():
551+ r_ct_q = models .Q (classifier_terms__in = self .classifier_terms .all ())
552+ r_qs = r_qs .annotate (r_ct = models .Count ("classifier_terms" , r_ct_q ))
553+ order_by .append ("-r_ct" )
554+
555+ # Order the related pages, then add distinct to deal with
556+ # annotating based on a many to many.
557+ if order_by :
558+ r_qs = r_qs .order_by (* order_by ).distinct ()
559+
560+ return r_qs [:num ]
561+
458562 def get_content_walls (self , check_child_setting = True ):
459563 current_content_walls = []
460564 if check_child_setting :
@@ -478,6 +582,7 @@ def get_context(self, request, *args, **kwargs):
478582 """
479583 context = super ().get_context (request )
480584
585+ # Show list of child pages.
481586 if self .index_show_subpages :
482587 # Get child pages
483588 all_children = self .get_index_children ()
@@ -528,9 +633,16 @@ def get_context(self, request, *args, **kwargs):
528633
529634 context ["index_paginated" ] = paged_children
530635 context ["index_children" ] = all_children
636+
637+ # Show a list of related pages.
638+ if self .related_show :
639+ context ["related_pages" ] = self .get_related_pages ()
640+
641+ # Content walls.
531642 context ["content_walls" ] = self .get_content_walls (
532643 check_child_setting = False
533644 )
645+
534646 return context
535647
536648
@@ -593,6 +705,9 @@ class Meta:
593705 abstract = True
594706
595707 template = "coderedcms/pages/article_page.html"
708+ search_template = "coderedcms/pages/article_page.search.html"
709+
710+ related_show_default = True
596711
597712 # Override body to provide simpler content
598713 body = StreamField (
@@ -1593,6 +1708,7 @@ class Meta:
15931708 abstract = True
15941709
15951710 template = "coderedcms/pages/form_page.html"
1711+ miniview_template = "coderedcms/pages/form_page.mini.html"
15961712 landing_page_template = "coderedcms/pages/form_page_landing.html"
15971713
15981714 base_form_class = WagtailAdminFormPageForm
0 commit comments