views:

83

answers:

2

I need to maintain a list of filtered and sorted objects, preferably in a generic manner, that can be used in multiple views. This is necessary so I can generate next, prev links, along with some other very useful things for the user.

Examples of filters:

field__isnull=True
field__exact="so"
field__field__isnull=False

Additionally, after the filtered query set is built, ordering may be applied by any of the fields.

My current solution is to use a FilterSpec class containing a collection of filters, along with an initial query set. This class is then serialized and passed to a view.

Consider a view with 25 dynamically filtered items. Each item in the view has a link to get a detailed view of the item. To each of these links, the serialized FilterSpec object of the current list is appended. So you end up with huge urls. Worse, the same huge filter is appended to all 25 links!

Another option is to store the FilterSpec in the session, but then you run into problems of when to delete the FilterSpec. Next you find all your views getting cluttered with code trying to determine if the filter should be deleted in preparation for a new list of objects.

I'm sure this problem has been solved before, so I'd love to hear other solutions that you guys have come up with.

+1  A: 

Depending on what you want to do, you'll want to either create a custom manager or add a new manager method.

In this example, you add a new manager that selects blog posts that are marked as published with a date after the current datetime.

from django.db import models
from datetime import datetime

class PublishedPostManager(models.Manager):
    def get_query_set(self):
        return super(PublishedPostManager, self).get_query_set().filter(published=True, time__lt=datetime.now())

class Post(models.Model):
    title = models.CharField(max_length=128)
    body = models.TextField()
    published = models.BooleanField(default=False)
    time = models.DateTimeField()

    objects = models.Manager() # Needed to ensure that the default manager is still available
    published = PublishedPostManager()

Then, instead of Post.objects.all(), you can use Post.published.all() to fetch all records. The normal QuerySet methods are available as well:

Post.published.count()
Post.published.select_related().filter(spam__iexact='eggs')
# etc

And of course, you can still use the default manager:

Post.objects.all()
cpharmston
Thanks for your answer; however, I already know how to perform the filtering. I'm trying to maintain a certain filter state over multiple views.
slypete
That's exactly what creating a custom manager does. In my example above, Post.objects.all() will return all instances of Post, while Post.published.all() will return all instances of Post where published=True and time__lt=datetime.now(). It does the filtering every time you use Post.published, and if you want to change it then you only have to do it in one place: the definition of PublishedPostManager.
cpharmston
There is some confusion. I agree that using a custom manager fits the filtering purpose. My question, however, poses this question: From a view, how do I choose which custom manager to use?
slypete
This is accurate information, but irrelevant to the question asked.
Carl Meyer
Oh, I misread your question. I was under the impression that when you said "filter the information" you were referring to Model.objects.filter(), not user filtering. My mistake.
cpharmston
+2  A: 

You've identified the two options for maintaining user-specific state in a web application: store it in cookies/session, or pass it around on URLs. I don't believe there's a third "silver bullet" waiting in the wings to solve your problem.

The URL query-string option has the advantage that a particular view state can be bookmarked, sent as an emailed URL, &c. It also may keep your view code a bit simpler, but at the cost of some extra template code to ensure the proper query-string always gets passed along on links.

In part your preferred solution may depend on the behavior you want. For instance, if a user bookmarks (or emails to a friend) the URL for a detail view of an item, do you want that URL to simply refer to the item itself, or to always carry along information about what list that item came out of? If the former, use session data. If the latter, use URLs with query strings.

In either case, I'm confident that the code that you find "cluttering all your views" can be refactored to be elegant, DRY, and as invisible as you want it to be. Decorators and/or class-based views might help.

Carl Meyer
Thanks Carl. Yes, I was hoping there was some third party "silver bullet" application that had already solved this problem. It's a clinical application, so URL sharing is not anticipated. I think the session is more appropriate, but let me ask you this: When leaving the detail view, how would you clear this session variable without clearing it from ALL views that the detail view links to?
slypete
@slypete Sorry, I don't understand your question. Why does the session variable need to be cleared? What do you mean by "when leaving the detail view"? When you clear a session variable, it's cleared from that session; any pages the user loads from that point on won't see the variable, unless another page sets it again. Somewhere here there's a miscommunication/misunderstanding about something, but I'm not clear where.
Carl Meyer
So upon entering this detail view, a session variable is stored with a sorted list of the items. How would you detect leaving this view to clear the session variable? If the session variable is not cleared, entering this view from a context that doesn't set the optional session variable would leave the user in the same list context as before.
slypete
The session approach may not be right for you. If you try to work with sessions in terms of "which page the user is coming from", you'll get nothing but headaches. Users open multiple tabs at once and do all kinds of strange things; you have to think of session data as persistent state for the entire browsing session, not as a way for one page to pass information to the "next". So set the session var whenever the user declares their desire to work with a given set of filters (however they do that in your app), and clear/change it whenever they declare otherwise.
Carl Meyer
You do want to keep in mind the "multiple tabs" multitasking scenario. By taking the session approach, you are probably preventing your users from working with two different filter sets at the same time in different tabs, which might be a big problem. Based on the way you are describing the problem, I am inclined to think the querystring approach is a better fit.
Carl Meyer
Thanks Carl, you provided me the reassurance that I was looking for.
slypete