فهرست منبع

Add ability to sort child pages by classifier (#438)

Under page Layout settings, a classifier can now be selected (Order child pages by classifier). This will first sort then child pages by the classifier terms' sort order (based on the terms selected on each child page). It will the second sort using the previous functionality (Order child pages by...). An example use-case is provided in the documentation.
Vince Salvino 3 سال پیش
والد
کامیت
d8b2db108a

+ 24 - 0
coderedcms/migrations/0023_auto_20210908_1702.py

@@ -0,0 +1,24 @@
+# Generated by Django 3.2.7 on 2021-09-08 21:02
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('coderedcms', '0022_auto_20210731_1853'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='coderedpage',
+            name='index_order_by_classifier',
+            field=models.ForeignKey(blank=True, help_text='Child pages will first be sorted following the order of this classifier’s terms (from Snippets > Classifiers).', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='coderedcms.classifier', verbose_name='Order child pages by classifier'),
+        ),
+        migrations.AlterField(
+            model_name='coderedpage',
+            name='index_order_by',
+            field=models.CharField(blank=True, choices=[('', 'Default Ordering'), ('-first_published_at', 'Date first published, newest to oldest'), ('first_published_at', 'Date first published, oldest to newest'), ('-last_published_at', 'Date updated, newest to oldest'), ('last_published_at', 'Date updated, oldest to newest'), ('title', 'Title, alphabetical'), ('-title', 'Title, reverse alphabetical')], default='', help_text='Child pages will then be sorted by this attribute.', max_length=255, verbose_name='Order child pages by'),
+        ),
+    ]

+ 38 - 1
coderedcms/models/page_models.py

@@ -186,12 +186,25 @@ class CoderedPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CoderedPageMeta):
         default=index_show_subpages_default,
         verbose_name=_('Show list of child pages')
     )
+    index_order_by_classifier = models.ForeignKey(
+        'coderedcms.Classifier',
+        blank=True,
+        null=True,
+        on_delete=models.SET_NULL,
+        related_name="+",
+        verbose_name=_('Order child pages by classifier'),
+        help_text=_(
+            'Child pages will first be sorted following the order of this '
+            'classifier’s terms (from Snippets > Classifiers).'
+        )
+    )
     index_order_by = models.CharField(
         max_length=255,
         choices=index_order_by_choices,
         default=index_order_by_default,
         blank=True,
         verbose_name=_('Order child pages by'),
+        help_text=_('Child pages will then be sorted by this attribute.')
     )
     index_num_per_page = models.PositiveIntegerField(
         default=10,
@@ -306,6 +319,7 @@ class CoderedPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CoderedPageMeta):
             [
                 FieldPanel('index_show_subpages'),
                 FieldPanel('index_num_per_page'),
+                FieldPanel('index_order_by_classifier'),
                 FieldPanel('index_order_by'),
                 FieldPanel('index_classifiers', widget=forms.CheckboxSelectMultiple()),
             ],
@@ -416,8 +430,31 @@ class CoderedPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CoderedPageMeta):
             query = querymodel.objects.child_of(self).live()
         else:
             query = self.get_children().live()
+
+        # Determine query sorting order.
+        order = []
+
+        # To sort by term order of a specific classifier, annotate the child
+        # pages with the `sort_order` of its ClassifierTerms.
+        if self.index_order_by_classifier:
+            terms = ClassifierTerm.objects.filter(
+                classifier=self.index_order_by_classifier,
+                # Reverse ManyToMany of `coderedpage.classifier_terms`.
+                coderedpage=models.OuterRef("pk"),
+            )
+            query = query.annotate(
+                term_sort_order=models.Subquery(terms.values("sort_order"))
+            )
+            order.append("term_sort_order")
+
+        # Second, order by the specified model attribute.
         if self.index_order_by:
-            return query.order_by(self.index_order_by)
+            order.append(self.index_order_by)
+
+        # Order the query.
+        if order:
+            query = query.order_by(*order)
+
         return query
 
     def get_content_walls(self, check_child_setting=True):

+ 80 - 1
coderedcms/models/tests/test_page_models.py

@@ -14,12 +14,14 @@ from coderedcms.models.page_models import (
     CoderedWebPage,
     get_page_models
 )
+from coderedcms.models.snippet_models import Classifier, ClassifierTerm
 from coderedcms.tests.testapp.models import (
     ArticleIndexPage,
     ArticlePage,
     EventIndexPage,
     EventPage,
     FormPage,
+    IndexTestPage,
     LocationIndexPage,
     LocationPage,
     StreamFormPage,
@@ -42,11 +44,13 @@ class BasicPageTestCase():
         self.homepage = WebPage.objects.get(url_path='/home/')
         self.homepage.add_child(instance=self.basic_page)
 
+    def tearDown(self):
+        self.basic_page.delete()
+
     def test_get(self):
         """
         Tests to make sure a basic version of the page serves a 200 from a GET request.
         """
-
         response = self.client.get(self.basic_page.url, follow=True)
         self.assertEqual(response.status_code, 200)
 
@@ -158,6 +162,9 @@ class CoderedStreamFormPageTestCase(AbstractPageTestCase, WagtailPageTests):
     model = CoderedStreamFormPage
 
 
+# -- CONCRETE PAGES ------------------------------------------------------------
+
+
 class ArticlePageTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
     model = ArticlePage
 
@@ -192,3 +199,75 @@ class LocationPageTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
 
 class StreamFormPageTestCase(ConcreteFormPageTestCase, WagtailPageTests):
     model = StreamFormPage
+
+
+# -- PAGES FOR TESTING SPECIFIC FUNCTIONALITY ----------------------------------
+
+
+class IndexTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
+    """
+    Tests indexing features (show/sort/filter child pages).
+    """
+    model = IndexTestPage
+
+    def setUp(self):
+        super().setUp()
+
+        # Create some child pages under this page.
+        self.child_1 = WebPage(title=f"{self.basic_page.title} - Child 1")
+        self.basic_page.add_child(instance=self.child_1)
+        self.child_2 = WebPage(title=f"{self.basic_page.title} - Child 2")
+        self.basic_page.add_child(instance=self.child_2)
+        self.child_3 = WebPage(title=f"{self.basic_page.title} - Child 3")
+        self.basic_page.add_child(instance=self.child_3)
+
+        # Create some classifier terms for general purpose use.
+        self.classifier = Classifier.objects.create(name="Classifier")
+        self.term_a = ClassifierTerm.objects.create(
+            classifier=self.classifier,
+            name="Term A",
+            sort_order=0,
+        )
+        self.term_b = ClassifierTerm.objects.create(
+            classifier=self.classifier,
+            name="Term B",
+            sort_order=1,
+        )
+
+    def tearDown(self):
+        super().tearDown()
+        self.classifier.delete()
+
+    def test_get_index_children(self):
+        """
+        Tests to make sure `get_index_children()` returns the correct queryset
+        based on selected page settings.
+        """
+        # Test it without setting any options, ensure it is not broken.
+        children = self.basic_page.get_index_children()
+        self.assertIn(self.child_1, children)
+        self.assertIn(self.child_2, children)
+        self.assertIn(self.child_3, children)
+
+        # Test index_order_by returns in the correct order.
+        self.basic_page.index_order_by = "title"
+        self.basic_page.save()
+        children = self.basic_page.get_index_children()
+        self.assertEqual(self.child_1, children[0])
+        self.assertEqual(self.child_2, children[1])
+        self.assertEqual(self.child_3, children[2])
+
+        # Test index_order_by classifier returns in the correct order.
+        self.basic_page.index_order_by_classifier = self.classifier
+        self.basic_page.index_order_by = "title"
+        self.basic_page.save()
+        self.child_3.classifier_terms.add(self.term_a)
+        self.child_3.save()
+        self.child_1.classifier_terms.add(self.term_b)
+        self.child_1.save()
+        self.child_2.classifier_terms.add(self.term_b)
+        self.child_2.save()
+        children = self.basic_page.get_index_children()
+        self.assertEqual(self.child_3, children[0])
+        self.assertEqual(self.child_1, children[1])
+        self.assertEqual(self.child_2, children[2])

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 25 - 0
coderedcms/tests/testapp/migrations/0005_auto_20210908_1741.py


+ 25 - 0
coderedcms/tests/testapp/migrations/0006_indextestpage.py

@@ -0,0 +1,25 @@
+# Generated by Django 3.2.7 on 2021-09-08 21:41
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('coderedcms', '0023_auto_20210908_1702'),
+        ('testapp', '0005_auto_20210908_1741'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='IndexTestPage',
+            fields=[
+                ('coderedpage_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='coderedcms.coderedpage')),
+            ],
+            options={
+                'verbose_name': 'Index Test Page',
+            },
+            bases=('coderedcms.coderedpage',),
+        ),
+    ]

+ 24 - 1
coderedcms/tests/testapp/models.py

@@ -1,3 +1,4 @@
+from coderedcms.models.page_models import CoderedPage
 from modelcluster.fields import ParentalKey
 from coderedcms.forms import CoderedFormField
 from coderedcms.models import (
@@ -129,7 +130,8 @@ class LocationPage(CoderedLocationPage):
 class LocationIndexPage(CoderedLocationIndexPage):
     """
     A page that holds a list of locations and displays them with a Google Map.
-    This does require a Google Maps API Key that can be defined in Settings > Google API Settings
+    This does require a Google Maps API Key that can be defined in Settings >
+    Google API Settings
     """
     class Meta:
         verbose_name = 'Location Landing Page'
@@ -152,3 +154,24 @@ class StreamFormPage(CoderedStreamFormPage):
 
 class StreamFormConfirmEmail(CoderedEmail):
     page = ParentalKey('StreamFormPage', related_name='confirmation_emails')
+
+
+"""
+--------------------------------------------------------------------------------
+CUSTOM PAGE TYPES for testing specific features. These should be based on
+CoderedPage when testing CoderedPage-specific functionality (which is where most
+of our logic lives).
+--------------------------------------------------------------------------------
+"""
+
+
+class IndexTestPage(CoderedPage):
+    """
+    Tests indexing features (show/sort/filter child pages).
+    """
+    class Meta:
+        verbose_name = "Index Test Page"
+
+    index_query_pagemodel = "testapp.WebPage"
+
+    template = 'coderedcms/pages/base.html'

+ 17 - 6
docs/features/page_types/web_pages.rst

@@ -56,16 +56,27 @@ layout. You have the following options:
 
 * **Template**:  The template you want the page to use to render.
 
-* **Show List of Child Pages**: Toggles whether this parent page should show a
+* **Show list of child pages**: Toggles whether this parent page should show a
   list of its children pages.
 
-* **Number Per Page**: Controls how many children pages you want to show at
+* **Number per page**: Controls how many children pages you want to show at
   once.
 
-* **Order Children Pages by**: Controls how the children pages are sorted on
-  this parent page.
-
-* **Filter Child Pages by**: Using Classifier terms, control which children
+* **Order child pages by classifier**: Child pages will first be sorted
+  following the order of this classifier's terms. For example, if this were set
+  to a classifier called "Status" (which contained terms "In Stock" and "Out of
+  Stock" in that respective order), then child pages that are "In Stock" would
+  be shown first before pages that are "Out of Stock".
+
+* **Order child pages by**: Controls how the children pages are sorted on
+  this parent page. For example, if "Title, alphabetically" were selected here,
+  the pages would be listed A-Z. If **Order child pages by classifier** is set
+  (above) then this ordering will be applied after the classifier ordering.
+  Following the previous example, if "Status" classifier were set above, "In
+  Stock" items would be sorted A-Z, then "Out of Stock" items would be sorted
+  A-Z.
+
+* **Filter child pages by**: Using Classifier terms, control which children
   pages are shown on the parent page.
 
 SEO Tab

+ 4 - 0
docs/releases/v0.22.0.rst

@@ -7,6 +7,10 @@ New features
 
 * Upgraded to Wagtail 2.14
 
+* Can now order child pages by Classifiers, using the Classifier Term's
+  sort order, in addition to ordering by model attributes. Read details in
+  :doc:`/features/page_types/web_pages`.
+
 
 Upgrade considerations
 ----------------------

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است