App integration

Here is how the Polls app (the one from the Django tutorial) works together with Django-pagetools in the demo.

The only changes i made to the version from the end of the tutorial was wrapping the templates in:

{% extends base.html %}

{% block main %}

old content

{% endblock main %}

The demo contains a main app where the integration is done, mostly in the app config:

from django.apps import AppConfig
from django.utils import timezone
from django.urls import reverse
from django.utils.text import slugify

from django.db.models.signals import post_save

from django.dispatch import receiver


# This will be added later to ``polls.models.Question``
def question_get_absolute_url(self):
    return reverse("polls:detail", args=(self.pk,))


# Notify subscribers about new questions
@receiver(post_save)
def questions_post_savecallback(sender, **kwargs):
    from pagetools.subscriptions.utils import to_queue

    if sender.__name__ == "Question" and kwargs["created"] == True:
        to_queue(
            {
                "title": "New Question",
                "body": "There is a great new question: %s." % (kwargs["instance"].question_text),
            }
        )


class MainConfig(AppConfig):
    name = "main"

    def ready(self):
        import polls.admin
        from polls.views import DetailView
        from polls.models import Question

        from pagetools.menus.models import MenuEntry
        from pagetools.menus.utils import (
            entrieable_auto_populated,
            entrieable_reverse_name,
        )
        from pagetools.menus.admin import make_entrieable_admin
        from pagetools.pages.models import Page
        import pagetools.search

        # To enable questions to be added easily to a menu, tweak their admin:
        make_entrieable_admin(polls.admin.QuestionAdmin)

        # But ...
        # A content_object in a `MenuEntry` needs `get_absolute_url`,
        # so add one to ``Question``.
        Question.add_to_class("get_absolute_url", question_get_absolute_url)
        # And make a menukey available for highlighting the active menuentry 
        Question.add_to_class("get_menukey", lambda q: "question:" + slugify(q))


        # For a dynamic menu entry with all questions as children
        # we need a function to define the dynamic entries:
        def recent_questions_as_entries():
            return [
                MenuEntry(title=q.question_text, content_object=q)
                for q in Question.objects.filter(pub_date__lte=timezone.now())
            ]

        # tell pagetools menus about it
        entrieable_auto_populated("All questions", recent_questions_as_entries)

        # Add "polls:index" as menu entry
        # entrieable_reverse_name("index", app_name="polls")
        # or
        entrieable_reverse_name("polls:index")

        # Give the polls detail view another pagetype:
        DetailView.pagetype_name = "special"

        # Make Questions searchable
        pagetools.search.search_mods = (
            (Question, ("question_text",)),
            # and pages also
            (Page, ("title", "content"), {"replacements": "content"}),
        )

There is also a templatetag for the latest question in`main`:

from django import template
from polls.models import Question


class LatestQuestionNode(template.Node):
    def render(self, context):
        qs = Question.objects.all().order_by("pub_date").reverse()
        if qs:
            question = qs[0]
            return '<a href="{url}">{text}</a>'.format(url=question.get_absolute_url(), text=question.question_text)
        return "No question available"

and it gets registered in the settings:

PT_TEMPLATETAG_WIDGETS = {
    'latest_question': 'main.templatetags.LatestQuestionNode',
    # ...
}

Now a TemplatetagWidget with the name ‘latest_questions’ can be created and added to a PagetypeArea.

Integration so far: a single question can be added to the menu; there is an entry with all children as subentries, a widget that shows the latest question and questions are searchable.

There is still one problem: All question detail pages would have no highlighting for the corresponding menu entry (the class="active" is missing on the list item).

The menu tag expects a parameter menukey - a string that indicates which entry is active. For pagetools’s models this is done in the pagetools.menus.views.SelectedMenuentriesMixin, so a subclass of ´´polls.views.DetailView´´ that inherits the mixin would solve this.

Another way is to overwrite the templates for the questions detail and index view.

detail.html:

{% load menus_tags %}

{% block menu %}
{% with question|slugify as menukey %}
{% menu "MainMenu" menukey %}
{% endwith %}
{% endblock menu %}

index.html:

{% load menus_tags %}

{% block menu %}
{% menu "MainMenu" "pollsindex" %}
{% endblock menu %}

Note: “pollsindex” is the slugified version of “polls:index”, see above.