Django: Should I use Query Strings or clean url's to map display parameters? And how? - django

I have the following urlconf:
urlpatterns = patterns('page.manager.views',
url(r'^$', 'pages', name='page_manager-pages'),
url(r'^add/$', 'add_page', name='page_manager-add_page'),
url(r'^(?P<page_id>\d+)/', include(object_patterns)),
)
The 'pages' view must return an object list of all pages. The user will be offered several display/search options through a side menu:
- created: any/ past 6hours/12hours/24hours/week
- Status: any/ status_1/status_2/status_3
- Duration: any / duration_1/duration_2/duration_3
etc.
These options are used to select which values should be presented to the user and can be used in combination, eg: created=any, status=status_1, duration=duration_1
My question is, how best to achieve this in Django?
What I have so far:
I can subclass a generic view for list objects, creating a view which takes the arguments(created, status, duration, etc) and provides the proper queryset(with the chosen[or default] ordering options passed along with the other arguments).
To pass these arguments, query strings seem to be right for this, since we are selecting from a resource(the list of all pages). Yes/no?
I'm also aware we get this information from request.GET.get('argument_name').
But how to create the links for the search options? eg: any, any/ status_1/status_2/status_3. We need to know which are already active, so...template tag? An easier way perhaps?
Is this the proper solution to handle this type of thing in Django, or is there a better way?

Since you have discrete, optional and unordered pieces of knowledge contributing to your query, I think that GET is the best way. Also, note that request.GET is a dict (ergo, you can do request.GET['status']).
As for the search options, I'd say a template tag and a context variable might either be appropriate depending on the details of your view. My most likely approach is to populate a context dict with True / False flags for which need to be displayed and then have {% if %} blocks to sort it out in the template.

Related

Django pass known exact string as a url parameter

I have two urls in dispatcher pointing the same view
path('posts/top/', posts, name='top'),
path('posts/new/', posts, name='new'),
I want view start as follows:
def posts(request, ordering):
...
I guess, to pass top and new as a parameter it should be something like:
path('posts/<ordering:top>/', posts, name='top'),
path('posts/<ordering:new>/', posts, name='new'),
But it gives me:
django.core.exceptions.ImproperlyConfigured: URL route 'posts/<ordering:top>/' uses invalid converter 'ordering'.
So, as a work around I use this, but it looks a little bit dirty:
path('posts/top/', posts, name='top', kwargs={'order': 'top'}),
path('posts/new/', posts, name='new', kwargs={'order': 'top'}),
What is the right way to do it?
You've misunderstood the way path converters work. The first element is the type, which here is str, and the second is the parameter name to use when calling the view. At this point you don't constrain the permissible values themselves. So your path would be:
path('posts/<str:ordering>/', posts, name='posts')
and when you come to reverse you would pass in the relevant parameter:
{% url 'posts' ordering='top' %}
If you really want to ensure that people can only pass those two values, you can either check it programmatically in the view, or you can use re_path to use regex in your path:
re_path(r'^posts/(?P<ordering>top|new)/$', posts, name='posts')

Using both sort & filter on a QuerySet

I have a list of userprofiles that I want to be able to sort and filter.
I have been able to do this manually, by manually typing in the URLs, but I haven't been able to code the template page to allow the persistence of a previously-applied filter or sort. Here is the url and template code that I currently have --
# in urls
url(r'^talent/filter\:(?P<position>[A-Za-z]+)/sort\:(?P<sort>[A-Za-z]+)$', 'talent_filter', name='talent_filter_sort'),
url(r'^talent/filter\:(?P<position>[A-Za-z]+)/$', 'talent_filter', name='talent_filter'),
url(r'^talent/sort\:(?P<sort>[A-Za-z]+)/$', 'talent_sort', name='talent_sort'),
url(r'^talent/$', 'talent', name='talent'),
# in template
<ul>
<li>SORT BY:</li>
<li>Alphabetical</li>
...
</ul>
<ul>
<li><a href = '{% url talent_filter position=position%}'>{{ position }}</a></li>
...
</ul>
Currently, if I am on the (unsorted, unfiltered) talent page, and I select a filter on the results, it will return
talent/filter:filter. Then, if I choose to sort the results, it (obviously) goes to talent/sort:sort, removing the previous filter.
What I want to accomplish is that if I am currently on talent/filter:filter and click a sort method, it will go to talent/filter:filter/sort:sort, and if I have already sorted the results (talent/sort:sort) and click on filter, it will also take me to talent/filter:filter/sort:sort. How would I accomplish this. Thank you.
I think one way you might be able to accomplish this is store a flag in your session that indicates what the sorting or filtering should be. For example, something like below could be used to save the state of the your sort choice.
request.session['TALANT_SORT'] = "alphabetical"
request.session['TALENT_FILTER'] = "top_ten"
And then your views can check for the existence the session keys and apply a filter accordingly.
qs = models.MyModel.objects.all()
sort = request.session.get('TALENT_SORT', None)
if sort in ["alphabetical", "created_date"]:
qs = qs.order_by(sort)
myfilter = request.session.get("TALENT_FILTER", None)
if myfilter in ["top_ten","bottom_ten"]:
qs = qs.filter( .... )
....
The sorting and filtering could then persist across requests.
If you want to remove the sort or filter, then perhaps you can delete the session keys in the view depending on some user action:
try:
del request.session['TALENT_SORT']
del request.session['TALENT_FILTER']
except KeyError:
pass
Also, depending on your requirements, you might consider combining the 2 urls into 1 and just use GET parameters to activate the sort.
request.GET.get('sort',None)
....
request.GET.get('filter', None)
....
These examples could probably use some more rigor, but that's the idea. Hope this helps.
Joe
Despite being slightly against what django is all about, the best approach to user specified sorts/filters if to use GET variables. So your URLs would appear like:
/talent/?filter=foo&sort=bar
Within your view set context variables for your current filter and sort then use those to build your urls in your templates.
Something like:
Alphabetical
If you really feel it's necessary to use url captured parameters you'll need to set up your urls to handle all the conditions (sort set, filter set, neither set, and both set). And then in your templates you'll need a bunch of if statements to select the correct url and parameters.
As I said this type of situation is much better handled with GET parameters.

Use url args in views, is it possible?

I'd like to use url arguments in views (not templates, I know how to do that).
So is it possible to use them like:
def item_link(self, item):
return mainpage_url_name + "%s/%i" % (item.slug, item.cid)
mainpage_url_name - is of course defined in url patterns (as name variable)
I'm a total newb in Django...
Thanks
First you should use names for your url patterns as documented here.
Then you can use reverse() to use these names in your views or methods.
Following your comments you are using the syndication framework.
Therefore you should make sure that you define get_absolute_url() for you models, ideally using the permalink decorator (for a clean reversing of your urls).
Looking at the example from Django's docs that should be all that's necessary.
To specify the contents of <link>, you
have two options. For each item in
items(), Django first tries calling
the item_link() method on the Feed
class. In a similar way to the title
and description, it is passed it a
single parameter, item. If that method
doesn't exist, Django tries executing
a get_absolute_url() method on that
object.

is there a way to use django generic views and some smart urlpatterns for quick ordering/sorting of queries?

let's assume I have a django model like this:
class Event(CommonSettings) :
author = models.ForeignKey(User)
timestamp = models.DateTimeField(auto_now_add=True)
event_type = models.ForeignKey(Event_Type, verbose_name="Event type")
text_field = models.TextField()
flag_box = models.BooleanField()
time = models.TimeField()
date = models.DateField()
project = models.ForeignKey(Project)
now, by default, I have a view where I sort all events by time & date:
event_list = Event.objects.filter().order_by('-date', '-time')
however, maybe the user wants to sort the events by time only, or by the date, or maybe in ascending order instead of descending. I know that I can create urlpatterns that match all these cases and then pass on the these options to my view, however I feel like I'm reinventing the wheel here. the django admin site can do all of this out of the box.
So here's my question: is there a clever, easy way of getting this done in a generic way, or do I have to hard code this for my models / views / templates?
and yes, I did find solutions like this (https://gist.github.com/386835) but this means you use three different projects to achieve one thing - this seems to be a too complicated solution for such a simple thing.
EDIT1:
how do I have to change the template so that I can combine multiple filters? Right now I have
Desc
Asc
but I want to allow the user to also change number of entries that get displayed. So I have:
order by date
order by name
This works all fine, but if I click on 'order by date' and then I click on 'Asc', then my previously selected order disappears. That's not what I want. I want the user to be able to combine some options but not others.
EDIT2:
unfortunately your solution doesn't work with
from django.views.generic.list_detail import object_list
and it's
paginate_by
option.
I tried:
prev
{% trans "next" %}
but the links then just don't work (nothing happens). maybe you need to do something special with "object_list"?
I don't think it's as much work as you're making it out to be - you can use variables instead of explicitly creating separate url patterns. If you look at how the django admin handles it, they tack on request variables to the url like ?ot=asc&o=2 This corresponds to sort in ascending order in by the 2nd column. Of course, if you designing a particular page, you might as well use more readable naming. So instead of numbering the categories, i'd do ?sort=desc&order_by=date and then put a regular expression in the view to match the different possibilities. Something like:
order = re.match(r"(?:date|time|name)$", request.GET['order_by'])
if request.GET['sort'] == 'desc':
order = '-' + order
results = Event.objects.filter().order_by(order)
You could instead use the regexp as a url pattern matcher as you suggested, but it's more common to let the url itself represent which part of the site you're at (i.e. website.com/events/) and the url request variables represent how that content is being displayed (i.e. ?order_by=date&sort=desc).
Hope that helps!
EDIT: For the second part of your question, use Django's templating system (which reads variables) instead of just html. There are several ways I can think of to do this, depending on personal preference and how exactly you want the UI to function (i.e. page loads with new variables anytime the user chooses a filter, or the user chooses all filter options in a form and then submits it so the page only has to reload once, etc). In this case, you could just do:
Ascending
Descending
Name
Date
Then in the view make sure your render_to_response arguments include a dictionary that looks like: {'order': request.GET['order_by'], 'sort': request.GET['sort_by'], }
Unfortunately, (and someone please correct me if I'm wrong) I don't think there's a template tag to generate a url with request.GET parameters - the url tag {% url name_of_view order_by=name sort_by=desc %} would generate "path/to/name_of_view/name/desc/", but I don't think there's a tag to generate "path/to/name_of_view?order_by=name&sort_by=desc". It would be pretty easy to write a custom tag for this though (I wouldn't be surprised if there's already one on django-snippets or something, although I just did a quick google search and didn't find anything).

django pagination: How to get number of page by id of element in the list

I use django-paginaton app and I'm very glad to use it. Now I need feature, that I don't know how to implement. I have list of elements, that paginated by django's paginator. I have one element with specified id, that I should show, but I don't know what page contains it. I need mechanism jump to the page, that contains my element.
I think it's a good idea if django-pagination will support this transparently and automatically. For example, I set special context variable page_by_id to X and if page is None, it will be defined to the value, that contains my X element.
What do you think about this mechanism?
May be there is another clean way to do this?
This is for continue of this questions:
Django pagination and "current page"
Django Pagination - Redirecting to the page an object is on
At least, I wrote patch for django-pagination that provide 2 features:
Get number of page by id of element inside paginated list;
Possibility to rename default parameter 'page' to another value for
using it inside template;
This works transparently by adding 2
optional parameters in autopaginate templatetag:
{% autopaginate object_list paginated_by orphans page_by_id parameter_name %}