'Practical' tips for working with Django
Entry updated Aug. 12, 2008 at 5:52 p.m.
Everything I did building the content management system for this Web site was wrong.
Actually, that might be an overstatement since this Web site works quite well (better than it did in Movable Type). But in the course of building a new Django content management system (CMS) that will drive the new, soon-to-be-launched Young Professionals of Knoxville Web site, I've learned a lot of useful techniques.
Much of my new-found knowledge is due to Django Release Manager James Bennett's wonderful book "Practical Django Projects."
Custom managers
One of the critical needs for the new YPK site is the posting and display of events. At first, I figured this would require a standard model with the following fields:
- slug
- name
- location (ForeignKey to Location subclass)
- description
- event date
- start time
- finish time
- attire (With two options: formal and casual)
- category (ForeignKey to Category app)
But then I realized that I would need a way to filter out events that are no longer current -- who wants to view old events?
That's where custom mangers in models come into play:
class CurrentManager(models.Manager):
def get_query_set(self):
return super(CurrentManager, self).get_query_set().filter(event_date__gte=datetime.date.today())
This allows me to filter all events whose date is greater than or equal to the current date. I then add the following to my model:
class Event(models.Model):
...
objects = models.Manager()
current = CurrentManager()
And in my project urls.py, I can return the following queryset:
event_info_dict = {
'queryset': Event.current.all(),
'date_field': 'event_date',
'allow_future': True,
}
No expired events will be displayed for that view.
I'm using the same technique to filter only press releases with a status of "live" for my news app. This would work well for blogs that have both "editing" and "live" status choices for entries.
Wrapping generic views
Another very useful technique in Django that plays off the custom manager is that of wrapping generic views with additional functions in another view.
For the YPK home page, I needed a way to pass extra context into the direct_to_template simple generic view without things getting caught in the queryset cache.
I created a new view -- the home page displays both current events and live press releases -- that contains the following:
from django.views.generic.simple import direct_to_template
from ypknox.apps.events.models import Event
from ypknox.apps.news.models import PressRelease
def home_page(request):
return direct_to_template(
request,
extra_context={'event_list': Event.current.all()[:4], 'pressrelease_list': PressRelease.live.all()[:6]},
template = 'home.html',
)
I knew I only needed four current events, and six live press releases, which is why I'm limiting the queries in this manner.
Now I can use the following on my home.html template to get the latest four current events:
{% for event in event_list %}
{{ event.name }}
{% endfor %}
I'm also using this technique of wrapping generic views to reduce the amount of code needed to get live press releases or current events for my Category app (both detail and list views).
Changing text to HTML before rendering the template
This should have been a no-brainer.
Since I'm using the Django "TextField" option in several of my app models, I need a way of converting the markup language from text to HTML. Prior to reading Bennett's book, I was doing this on the template with the markdown filter.
But I've learned since that this is an intensive process that needs to only be done once, and stored in the database for retrieval.
Here's what I'm doing now (assumes Python Markdown module is installed):
from markdown import markdown
class PressRelease(models.Model):
...
summary = models.TextField()
summary_html = models.TextField(editable=False, blank=True)
def save(self):
self.summary_html = markdown(self.summary)
super(PressRelease, self).save()
This applies the markdown filter when saving the object in the database, and populates the summary_html field with the marked-up text. Then, on my template, I simple call the summary_html like this:
{{ object.summary_html|safe }}
The safe filter is applied because I know the markup is OK to be applied with encoding for security reasons.
There are a lot more Django tips I'm picking up as I finish the new YPK site. I'll post those, with a link to the finished site when it's complete.
In the meantime, please share your thoughts on these tips, or others like them, in the comments.
UPDATE (8/12/08): There was an error in the model code snippet showing the manager within the model class. This has been fixed. I've also added a way to show how you can still get all of the events, not just current events, using objects = models.Manager() or events.objects.all().
13 comments
Great post. Looks like I'm doing some of the same things you did at first with a project I'm working on now (specifically, using markdown filters in templates for things that only need to be converted once). I'll have to try doing it on the database side.
I might have to get the book.
Out of curiosity, are you working from .96 or the trunk?
@Chris Amico
Glad I could be of help!
I've been working from trunk on my Django apps since starting with the framework about a year or so ago. It's always been stable for my needs.
Ideally, in general you'll want to save both the original text written in Markdown and the HTML one that Markdown provides (you save only the latter).
The reason is that if you ever want to provide an interface for editing the summary later on, you can then retrieve the original version written with Markdown, and have the user edit that (as opposed to having him/her edit the HTML version).
Just a thought.
To be honest, on my site I just apply the Textile filter. No one goes to the site, so I have no worries about resources.[HTML_REMOVED]
@Beetle B.
Thanks for the comment!
Actually, I do save both in my real app models. I only put the
summary_html = models.TextField(editable=False, blank=True)for the example.To be more clear, I've updated the code snippet.
Nice rundown of a couple key features covered in the book. He gives some really practical approaches in the book... just as one would expect from the title!
Good collection of examples patrick. The 'Custom Manager' is something I am looking forward to play with. Thank you
@Chris and @Yashh
Thanks! Glad you enjoyed the entry.
Hi Patrick. Do you know if there's a way to use markdown extensions such as abbreviations and footnotes when using the save to html technique?
I'm not sure HTML for
abbris in the original spec for Markdown. There are extensions in Python that account for this however.BTW, shouldn't that be:
?
@Chris
Indeed. Thanks for spotting that!
Wouldn't it be a better idea to instead implement the caching system so that your entire template, and the markdown inside it, is cached? Seems like that would be a cleaner, and probably more performant way of optimizing than having multiple fields in your model.
@Ian
I was actually using that method prior to going with the one described in this entry.
But I don't think using the markdown filter with caching in your templates is going to be better performing than running the filter once, saving the output, and grabbing it for the template.
Yes, it's slightly redundant in the model, but I believe the benefit is there.
Post a comment
Please use Markdown syntax for formatting. No HTML is allowed. By using this comment form, it's assumed that you agree with the terms of my comment policy.