Wednesday, 3 September 2008

5. Expanding the Weblog

Writing the Link Model

This is part 5 of a series of posts on James Bennett's excellent Practical Django Projects. The table of contents and explanation can be found here.

I'll move straight into the material here as it follows on from the previous chapter expanding the weblog application.

On p78 remember to move the prepopulate argument to your admin class which is created on p79. Update your admin.py and add in the LinkAdmin class in addition to your CategoryAdmin and EntryAdmin.
#admin.py

from coltrane.models import Category, Entry, Link
...
class LinkAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug":("title",)}

admin.site.register(Link, LinkAdmin)
...
Installing the pydelicious module on p79 also requires the feedparser module from
http://code.google.com/p/feedparser/

Setting Up Views for Categories

I still had some redundant code left over in the views.py from Chapter 4. The entries_index function was replaced by the generic view archive_index in the last chapter so remove this and the Entry import.

On p87 there is a minor type with an extra unwanted ) in extra_context={ 'category': category })

Cleaning Up the URLConf

This section breaks the urls.py into separate files. When you delete the urls.py delete your urls.pyc as well - I don't think it matters in this case as python imports the urls folder modules first, but good to get in the habit. I updated my tests.py to reflect the new urls layout.
from coltrane import admin, models, views
from coltrane.urls import categories, entries, links, tags

Handling Live Entries

Custom managers for your models are covered in this section. On p94 here are two lines that define the managers.
 live = LiveEntryManager()
 objects = models.Manager()
The order is important because the first manager becomes the default manager for the class. Normally this would be great but in 1.x this won't work with admin as the admin model will also use the default manager. This means that if you set your status to draft, your entries will disappear from the admin interface; not what you wanted.

In principle what you want to be able to do is define which manager the Admin class will use in your custom EntryAdmin class, but in practice this is tricky to do at the moment because there are a lot of functions to override.

I suspect this behaviour will be changed at some point and there is a ticket already that relates to this: http://code.djangoproject.com/ticket/7510

So for now just switch the order around to put objects = models.Manager() first.

Finally, mention was made in Chapter 4 about decoupling the Category model urls but there was no code example. So update your Category model like this:
    def get_absolute_url(self):
        return ('coltrane_category_detail', (),
             { 'slug': self.slug })
    get_absolute_url = models.permalink(get_absolute_url)
In your urls.categories.py you'll need to update your urlpatterns to this:
urlpatterns = patterns('',
    (r'^$', 'django.views.generic.list_detail.object_list',
    { 'queryset': Category.objects.all() }),
    (r'^(?P<slug>[-\w]+)/$',
        'coltrane.views.category_detail',{},
        'coltrane_category_detail'),
)
Because you aren't using a generic view for this you don't need to pass in an info_dict like your url.links.py so just pass an empty dictionary {}.

There is one new method in 1.0 for naming your urlpatterns to avoid bumping into other patterns that might use coltrane.views.category_detail. It seems like good practice to use that anyway here, so re-write the code like this.
urlpatterns = patterns('',
    (r'^$', 'django.views.generic.list_detail.object_list',
    { 'queryset': Category.objects.all() }),
    url(r'^(?P<slug>[-\w]+)/$',
        'coltrane.views.category_detail',
         name='coltrane_category_detail'),
)
Now you can write a proper template for category_detail.html if you wish. Remember that your view is a list so you need a {% for category in object_list %} block. Viewing the category detail view from the admin doesn't make a lot of sense but I guess you would normally view it in the context of an entry. The main thing to watch out for here is when testing the view to make sure you're are actually using a category that is attached to a live entry, but for now lets get onto chapter 6.

5 comments:

Fernando Correia said...

Very helpful. Thanks!

W.A.S.T.E. said...

It seems sorta straightforward once you do it... it might be helpful for others to know... If you want to access particular category data when building the category_detail_view, modify your category_detail view as follows:

def category_detail(request, slug):
category = get_object_or_404(Category, slug=slug)
return render_to_response('coltrane/category_detail.html',
{'category': category,
'object_list': category.live_entry_set() })

josebrwn said...

On p. 94-95 we add a method on the Category model, "live_entry_set". When calling it from the category_detail view I get a NameError, Exception Value:

global name 'entry' is not defined

That's when the Category class is above the Entry class in models.py. If I put Category after entry the site breaks with:

categories = models.ManyToManyField(Category)
NameError: name 'Category' is not defined

So it seems like a chicken and egg problem. How do you get around this?

Brett said...

josebrwn. Capital E for entry??

josebrwn said...

Yes, capital E for Entry in live_entry_set, thank you!!

ps awesome blog, absolute lifesaver

Hedged Down