djangozinnia

How can I register templatetag in Zinnia for displaying latest entries in specific category?


I'm building a homepage for a django project with Zinnia installed that will show the latest entries from each category. Here, Fantomas42 suggested that registering a new templatetag that took the get_recent_entries tag and added a filter clause would be the best way to achieve this.

I tried to look at the other templatetags to gather how to write this filter clause through context clues but the tags are designed to work dynamically, rather than grab anything specifically named, so I couldn't quite parse how to write a clause that would filter for a specific category.

I'm not sure whether it'd be best to write the clause to filter for a slug (in this case, the slug for the category is political-beat), category name via string ("The Political Beat"), or via the category's position in the category tree (this would be position 1, as it's the only category registered this far -- unless it would be 0... again, I really wish I had time to step back and take a few python tutorials...).

For context, here are some of the other templatetags registered by Zinnia:

@register.inclusion_tag('zinnia/tags/dummy.html', takes_context=True)
    def get_categories(context, template='zinnia/tags/categories.html'):
    """
    Return the published categories.
    """
    return {'template': template,
            'categories': Category.published.all().annotate(
                count_entries_published=Count('entries')),
            'context_category': context.get('category')}


@register.inclusion_tag('zinnia/tags/dummy.html', takes_context=True)
def get_categories_tree(context,
template='zinnia/tags/categories_tree.html'):
    """
    Return the categories as a tree.
    """
    return {'template': template,
            'categories': Category.objects.all(),
            'context_category': context.get('category')}


@register.inclusion_tag('zinnia/tags/dummy.html', takes_context=True)
def get_authors(context, template='zinnia/tags/authors.html'):
    """
    Return the published authors.
    """
    return {'template': template,
            'authors': Author.published.all().annotate(
                count_entries_published=Count('entries')),
            'context_author': context.get('author')}


@register.inclusion_tag('zinnia/tags/dummy.html')
def get_recent_entries(number=5,
template='zinnia/tags/entries_recent.html'):
    """
    Return the most recent entries.
    """
    return {'template': template,
            'entries': Entry.published.all()[:number]}



@register.inclusion_tag('zinnia/tags/dummy.html')
def get_featured_entries(number=5,
                         template='zinnia/tags/entries_featured.html'):
    """
    Return the featured entries.
    """
    return {'template': template,
            'entries': Entry.published.filter(featured=True)[:number]}


@register.inclusion_tag('zinnia/tags/dummy.html')
def get_draft_entries(number=5,
                      template='zinnia/tags/entries_draft.html'):
    """
    Return the last draft entries.
    """
    return {'template': template,
            'entries': Entry.objects.filter(status=DRAFT)[:number]}

I'm kind of experimenting with the solution blindly, but if I happen to stumble upon it, I'll update with an answer!

EDIT: Here's a photo of the home template I'm integrating with Zinnia, in case it's helpful in clarifying the goal of creating the new templatetag(s).Photo of homepage.


Solution

  • This isn't the prettiest solution (mainly because you have to select your category in two places, if you want to keep your sitemap in order), but it does allow you to get recent entries based on their respective categories, and I want to share it in case somebody else who lacks thorough expertise in python is looking to implement this functionality.

    And, if you are proficient with python, maybe this solution will upset you enough to post a better one :)

    From the context clues I could gather from Zinnia's other templatetags, I deduced that if "Featured=True" filtered by "Featured" entries, then maybe I could extend the entry model in the admin to included other types of "Featured" checkboxes, which would correspond with the categories of the entry posted. In this case, I added "Featured_politics."

    I then made changes to the following files (essentially, I searched for "featured" throughout the project, and copy/pasted the corresponding code, and changed "featured" to "featured_politics"):

    zinnia/admin/entry.py

    class EntryAdmin(admin.ModelAdmin):
        """
        Admin for Entry model.
        """
        form = EntryAdminForm
        date_hierarchy = 'publication_date'
        fieldsets = (
            (_('Content'), {
            'fields': (('title', 'status'), 'lead', 'content',)}),
            (_('Illustration'), {
                'fields': ('image', 'image_caption'),
                'classes': ('collapse', 'collapse-closed')}),
            (_('Publication'), {
                'fields': ('publication_date', 'sites',
                           ('start_publication', 'end_publication')),
                'classes': ('collapse', 'collapse-closed')}),
            (_('Discussions'), {
                'fields': ('comment_enabled', 'pingback_enabled',
                           'trackback_enabled'),
                'classes': ('collapse', 'collapse-closed')}),
            (_('Privacy'), {
                'fields': ('login_required', 'password'),
                'classes': ('collapse', 'collapse-closed')}),
            (_('Templates'), {
                'fields': ('content_template', 'detail_template'),
                'classes': ('collapse', 'collapse-closed')}),
            (_('Metadatas'), {
                'fields': ('featured', 'featured_politics', 'excerpt', 'authors', 'related'),
                'classes': ('collapse', 'collapse-closed')}),
            (None, {'fields': ('categories', 'tags', 'slug')}))
        list_filter = (CategoryListFilter, AuthorListFilter,
                       'publication_date', 'sites', 'status')
        list_display = ('get_title', 'get_authors', 'get_categories',
                        'get_tags', 'get_sites', 'get_is_visible', 'featured',
                        'get_short_url', 'publication_date')
        radio_fields = {'content_template': admin.VERTICAL,
                        'detail_template': admin.VERTICAL}
        filter_horizontal = ('categories', 'authors', 'related')
        prepopulated_fields = {'slug': ('title', )}
        search_fields = ('title', 'excerpt', 'content', 'tags')
        actions = ['make_mine', 'make_published', 'make_hidden',
                   'close_comments', 'close_pingbacks', 'close_trackbacks',
                   'ping_directories', 'put_on_top',
                   'mark_featured', 'mark_featured_poltics', 'unmark_featured_poltics', 'unmark_featured']
    
    
    
    def mark_featured_politics(self, request, queryset):
            """
            Mark selected as featured post.
            """
            queryset.update(featured_politics=True)
            self.message_user(
                request, _('Selected entries are now marked as featured in politics.'))
        mark_featured_politics.short_description = _('Mark selected entries as featured in politics.')
    
        def unmark_featured(self, request, queryset):
            """
            Un-Mark selected featured posts.
            """
            queryset.update(featured=False)
            self.message_user(
                request, _('Selected entries are no longer marked as featured.'))
        unmark_featured.short_description = _(
            'Unmark selected entries as featured')
    
        def unmark_featured_politics(self, request, queryset):
            """
            Un-Mark selected featured posts.
            """
            queryset.update(featured_politics=False)
            self.message_user(
                request, _('Selected entries are no longer marked as featured in politics.'))
        unmark_featured_politics.short_description = _(
        'Unmark selected entries as featured in politics.')
    

    zinnia/fixtures/helloworld.json (not sure if this was necessary, but this is one excerpt from several -- that look the exact same -- changes that I made in here).

          "featured": false,
          "featured_politics": false,
          "start_publication": null,
          "pingback_enabled": true,
          "trackback_enabled": true,
          "authors": [
    

    zinnia/models_bases/entry.py

    class FeaturedEntry(models.Model):
        """
        Abstract model class to mark entries as featured.
        """
        featured = models.BooleanField(
            _('featured'), default=False)
    
        class Meta:
            abstract = True
    
    class FeaturedEntryPolitics(models.Model):
        """
        Abstract model class to mark entries as featured.
        """
        featured_politics = models.BooleanField(
            _('featured_politics'), default=False)
    
        class Meta:
            abstract = True
    

    And, at the bottom of models_bases/entry.py, update this list to include your new class(es):

    class AbstractEntry(
            CoreEntry,
            ContentEntry,
            DiscussionsEntry,
            RelatedEntry,
            LeadEntry,
            ExcerptEntry,
            ImageEntry,
            FeaturedEntry,
            FeaturedEntryPolitics,
            AuthorsEntry,
            CategoriesEntry,
            TagsEntry,
            LoginRequiredEntry,
            PasswordRequiredEntry,
            ContentTemplateEntry,
            DetailTemplateEntry):
    

    zinnia/templatetags/zinnia.py

    @register.inclusion_tag('zinnia/tags/dummy.html')
    def get_featured_entries(number=5,
                             template='zinnia/tags/entries_featured.html'):
        """
        Return the featured entries.
        """
        return {'template': template,
                'entries': Entry.published.filter(featured=True)[:number]}
    
    @register.inclusion_tag('zinnia/tags/dummy.html')
    def get_politics_entries(number=5,
                             template='recent_politics.html'):
        """
        Return the featured entries.
        """
        return {'template': template,
                'entries': Entry.published.filter(featured_politics=True)[:number]}
    

    zinnia/xmlrpc/metaweblog.py

      # Useful Wordpress Extensions
                'wp_author': author.get_username(),
                'wp_author_id': author.pk,
                'wp_author_display_name': author.__str__(),
                'wp_password': entry.password,
                'wp_slug': entry.slug,
                'sticky': entry.featured,
                'sticky': entry.featured_politics}
    
    
    @xmlrpc_func(returns='struct[]', args=['string', 'string', 'string'])
    def get_users_blogs(apikey, username, password):
        """
        blogger.getUsersBlogs(api_key, username, password)
        => blog structure[]
        """
        authenticate(username, password)
        site = Site.objects.get_current()
        return [blog_structure(site)]
    
    
    @xmlrpc_func(returns='struct', args=['string', 'string', 'string'])
    def get_user_info(apikey, username, password):
        """
        blogger.getUserInfo(api_key, username, password)
        => user structure
        """
        user = authenticate(username, password)
        site = Site.objects.get_current()
        return user_structure(user, site)
    
    
    @xmlrpc_func(returns='struct[]', args=['string', 'string', 'string'])
    def get_authors(apikey, username, password):
        """
        wp.getAuthors(api_key, username, password)
        => author structure[]
        """
        authenticate(username, password)
        return [author_structure(author)
                for author in Author.objects.filter(is_staff=True)]
    
    
    @xmlrpc_func(returns='boolean', args=['string', 'string',
                                          'string', 'string', 'string'])
    def delete_post(apikey, post_id, username, password, publish):
        """
        blogger.deletePost(api_key, post_id, username, password, 'publish')
        => boolean
        """
        user = authenticate(username, password, 'zinnia.delete_entry')
        entry = Entry.objects.get(id=post_id, authors=user)
        entry.delete()
        return True
    
    
    @xmlrpc_func(returns='struct', args=['string', 'string', 'string'])
    def get_post(post_id, username, password):
        """
        metaWeblog.getPost(post_id, username, password)
        => post structure
        """
        user = authenticate(username, password)
        site = Site.objects.get_current()
        return post_structure(Entry.objects.get(id=post_id, authors=user), site)
    
    
    @xmlrpc_func(returns='struct[]',
                 args=['string', 'string', 'string', 'integer'])
    def get_recent_posts(blog_id, username, password, number):
        """
        metaWeblog.getRecentPosts(blog_id, username, password, number)
        => post structure[]
        """
        user = authenticate(username, password)
        site = Site.objects.get_current()
        return [post_structure(entry, site)
                for entry in Entry.objects.filter(authors=user)[:number]]
    
    
    @xmlrpc_func(returns='struct[]', args=['string', 'string', 'string'])
    def get_tags(blog_id, username, password):
        """
        wp.getTags(blog_id, username, password)
        => tag structure[]
        """
        authenticate(username, password)
        site = Site.objects.get_current()
        return [tag_structure(tag, site)
                for tag in Tag.objects.usage_for_queryset(
                    Entry.published.all(), counts=True)]
    
    
    @xmlrpc_func(returns='struct[]', args=['string', 'string', 'string'])
    def get_categories(blog_id, username, password):
        """
        metaWeblog.getCategories(blog_id, username, password)
        => category structure[]
        """
        authenticate(username, password)
        site = Site.objects.get_current()
        return [category_structure(category, site)
                for category in Category.objects.all()]
    
    
    @xmlrpc_func(returns='string', args=['string', 'string', 'string', 'struct'])
    def new_category(blog_id, username, password, category_struct):
        """
        wp.newCategory(blog_id, username, password, category)
        => category_id
        """
        authenticate(username, password, 'zinnia.add_category')
        category_dict = {'title': category_struct['name'],
                         'description': category_struct['description'],
                         'slug': category_struct['slug']}
        if int(category_struct['parent_id']):
            category_dict['parent'] = Category.objects.get(
                pk=category_struct['parent_id'])
        category = Category.objects.create(**category_dict)
    
        return category.pk
    
    
    @xmlrpc_func(returns='string', args=['string', 'string', 'string',
                                     'struct', 'boolean'])
    def new_post(blog_id, username, password, post, publish):
        """
        metaWeblog.newPost(blog_id, username, password, post, publish)
        => post_id
        """
        user = authenticate(username, password, 'zinnia.add_entry')
        if post.get('dateCreated'):
            creation_date = datetime.strptime(
                post['dateCreated'].value[:18], '%Y-%m-%dT%H:%M:%S')
            if settings.USE_TZ:
                creation_date = timezone.make_aware(
                    creation_date, timezone.utc)
        else:
            creation_date = timezone.now()
    
        entry_dict = {'title': post['title'],
                      'content': post['description'],
                      'excerpt': post.get('mt_excerpt', ''),
                      'publication_date': creation_date,
                      'creation_date': creation_date,
                      'last_update': creation_date,
                      'comment_enabled': post.get('mt_allow_comments', 1) == 1,
                      'pingback_enabled': post.get('mt_allow_pings', 1) == 1,
                      'trackback_enabled': post.get('mt_allow_pings', 1) == 1,
                      'featured': post.get('sticky', 0) == 1,
                      'featured_politics': post.get('sticky', 0) == 1,
                      'tags': 'mt_keywords' in post and post['mt_keywords'] or '',
                      'slug': 'wp_slug' in post and post['wp_slug'] or slugify(
                          post['title']),
                      'password': post.get('wp_password', '')}
        if user.has_perm('zinnia.can_change_status'):
            entry_dict['status'] = publish and PUBLISHED or DRAFT
    
        entry = Entry.objects.create(**entry_dict)
    
        author = user
        if 'wp_author_id' in post and user.has_perm('zinnia.can_change_author'):
            if int(post['wp_author_id']) != user.pk:
                author = Author.objects.get(pk=post['wp_author_id'])
        entry.authors.add(author)
    
        entry.sites.add(Site.objects.get_current())
        if 'categories' in post:
            entry.categories.add(*[
                Category.objects.get_or_create(
                    title=cat, slug=slugify(cat))[0]
                for cat in post['categories']])
    
        return entry.pk
    
    
    @xmlrpc_func(returns='boolean', args=['string', 'string', 'string',
                                          'struct', 'boolean'])
    def edit_post(post_id, username, password, post, publish):
        """
        metaWeblog.editPost(post_id, username, password, post, publish)
        => boolean
        """
        user = authenticate(username, password, 'zinnia.change_entry')
        entry = Entry.objects.get(id=post_id, authors=user)
        if post.get('dateCreated'):
            creation_date = datetime.strptime(
                post['dateCreated'].value[:18], '%Y-%m-%dT%H:%M:%S')
            if settings.USE_TZ:
                creation_date = timezone.make_aware(
                    creation_date, timezone.utc)
        else:
            creation_date = entry.creation_date
    
        entry.title = post['title']
        entry.content = post['description']
        entry.excerpt = post.get('mt_excerpt', '')
        entry.publication_date = creation_date
        entry.creation_date = creation_date
        entry.last_update = timezone.now()
        entry.comment_enabled = post.get('mt_allow_comments', 1) == 1
        entry.pingback_enabled = post.get('mt_allow_pings', 1) == 1
        entry.trackback_enabled = post.get('mt_allow_pings', 1) == 1
        entry.featured = post.get('sticky', 0) == 1
        entry.featured_politics = post.get('sticky', 0) == 1
    

    Don't forget that you've made changes to your models, so you need to run migrations!

    And now, I can call the latest entries that I want to appear under a (in this example) "Politics" header on the homepage by using {% get_politics_entries 3 template="homepage_latest_politics.html" %}.

    Not entirely relevant, but just in case it's helpful to anybody, my template for homepage_latest_politics.html is:

    {% load i18n %}
    <div class="row {% if not entries %}no-{% endif %}entries-featured_poltics">
      {% for entry in entries %}
            <div class="col s4">
              <div class="card large">
                <div class="card-image">
                  <img src="{% if entry.image %}{{ entry.image.url }}{% endif %}" alt="{{ entry.title }}">
                  <span class="card-title">{{ entry.title }}</span>
                </div>
                <div class="card-content">
                  <p>{{ entry.excerpt|safe|linebreaks|truncatewords_html:30 }}</p>
                </div>
                <div class="card-action">
                  <a href="#">Click to read more.</a>
                </div>
              </div>
            </div>
           {% endfor %}
    </div>
    

    This is an integration of zinnia's tags with the "Card" component of Materialize, and produces this -- with the entries being the latest to have the "Featured_politics" box checked:

    enter image description here

    enter image description here

    Hey -- I did say that it wasn't the prettiest solution, but... it works!