Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django manager for _set in model

I'm in the progress of learning Django at the moment but I can't figure out how to solve this problem on my own. I'm reading the book Developers Library - Python Web Development With Django and in one chapter you build a simple CMS system with two models (Story and Category), some generic and custom views together with templates for the views.

The book only contains code for listing stories, story details and search. I wanted to expand on that and build a page with nested lists for categories and stories.

- Category1
-- Story1
-- Story2
- Category2
- Story3 etc.

I managed to figure out how to add my own generic object_list view for the category listing. My problem is that the Story model have STATUS_CHOICES if the Story is public or not and a custom manager that'll only fetch the public Stories per default. I can't figure out how to tell my generic Category list view to also use a custom manager and only fetch the public Stories. Everything works except that small problem. I'm able to create a list for all categories with a sub list for all stories in that category on a single page, the only problem is that the list contains non public Stories.

I don't know if I'm on the right track here. My urls.py contains a generic view that fetches all Category objects and in my template I'm using the category.story_set.all to get all Story objects for that category, wich I then loop over.

I think it would be possible to add a if statement in the template and use the VIEWABLE_STATUS from my model file to check if it should be listed or not. The problem with that solution is that it's not very DRY compatible.

Is it possible to add some kind of manager for the Category model too that only will fetch in public Story objects when using the story_set on a category?

Or is this the wrong way to attack my problem?

Related code

urls.py (only category list view):

urlpatterns += patterns('django.views.generic.list_detail',
    url(r'^categories/$', 'object_list', {'queryset': Category.objects.all(),
                                          'template_object_name': 'category'
                                         }, name='cms-categories'),

models.py:

from markdown import markdown
import datetime
from django.db import models
from django.db.models import permalink
from django.contrib.auth.models import User

VIEWABLE_STATUS = [3, 4]

class ViewableManager(models.Manager):
    def get_query_set(self):
        default_queryset = super(ViewableManager, self).get_query_set()
        return default_queryset.filter(status__in=VIEWABLE_STATUS)

class Category(models.Model):
    """A content category"""
    label = models.CharField(blank=True, max_length=50)
    slug = models.SlugField()

    class Meta:
        verbose_name_plural = "categories"

    def __unicode__(self):
        return self.label

    @permalink
    def get_absolute_url(self):
        return ('cms-category', (), {'slug': self.slug})

class Story(models.Model):
    """A hunk of content for our site, generally corresponding to a page"""

    STATUS_CHOICES = (
        (1, "Needs Edit"),
        (2, "Needs Approval"),
        (3, "Published"),
        (4, "Archived"),
    )

    title = models.CharField(max_length=100)
    slug = models.SlugField()
    category = models.ForeignKey(Category)
    markdown_content = models.TextField()
    html_content = models.TextField(editable=False)
    owner = models.ForeignKey(User)
    status = models.IntegerField(choices=STATUS_CHOICES, default=1)
    created = models.DateTimeField(default=datetime.datetime.now)
    modified = models.DateTimeField(default=datetime.datetime.now)

    class Meta:
        ordering = ['modified']
        verbose_name_plural = "stories"

    def __unicode__(self):
        return self.title

    @permalink
    def get_absolute_url(self):
        return ("cms-story", (), {'slug': self.slug})

    def save(self):
        self.html_content = markdown(self.markdown_content)
        self.modified = datetime.datetime.now()
        super(Story, self).save()

    admin_objects = models.Manager()
    objects = ViewableManager()

category_list.html (related template):

{% extends "cms/base.html" %}
{% block content %}
    <h1>Categories</h1>
    {% if category_list %}
        <ul id="category-list">
        {% for category in category_list %}
            <li><a href="{{ category.get_absolute_url }}">{{ category.label }}</a></li>
            {% if category.story_set %}
                <ul>
                    {% for story in category.story_set.all %}
                        <li><a href="{{ story.get_absolute_url }}">{{ story.title }}</a></li>
                    {% endfor %}
                </ul>
            {% endif %}
        {% endfor %}
        </ul>
    {% else %}
        <p>
            Sorry, no categories at the moment.
        </p>
    {% endif %}
{% endblock %}
like image 659
Daniel Johansson Avatar asked Dec 01 '22 08:12

Daniel Johansson


1 Answers

I find the documentation slightly unclear, but the use_for_related_fields Manager attribute may be what you're looking for. When you're trying it, note that this functionality uses _default_manager, which will always point at the first Manager declared in a Model class, regardless of what attribute name it's been given. You can always customise which items are editable in your admin site by overriding the queryset method in the appropriate ModelAdmin class instead.

Failing that...

class Category(models.Model):
    def public_stories(self):
        return Story.objects.filter(category=self)

...and...

{% for story in category.public_stories %}

This at least avoids duplicating the logic which determines which stories are viewable.

like image 133
Jonny Buchanan Avatar answered Dec 04 '22 03:12

Jonny Buchanan