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

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).

Related

Django: Generate form from dictionary

I'm currently working on a django-app where users have the option to select synonyms for a select number of words. These words are then replaced all over the website by these synonyms
these synonymes are defined as a separate model:
class TransportSynonyms(models.Model):
user= models.ForeignKey(user)
key = models.CharField(max_length=255)
value = models.CharField(max_length=255)
This process is done in part with template tags, and as such the number of words that can be 'synonymed' is limited. For example, the following words can be replaced by synonymes:
'train', 'plane'.
And appear like so in the html template:
{% get_trans "train" %}
{% get_trans "plane" %}
I now want to give user the ability to define synonymes for themselves, without the admin view. I created a few pages using a ListView as an overview (so the users could see which words they can change) with individual buttons that led to EditViews.
However, I'm since user have no synonyms linked to them by default, the ListView appears empty. I could solve that by passing a list from the view, but that list wont have the required '.id' values and would be worthless for linking to the EditViews.
My listview currently looks like the following table:
Original | Synonym
---------------------------
train | train (button to editview)
plane | aircraft (button to editview)
The buttons require an ID value
href="{% url 'synonym_edit' pk=synonym.id %}"
I have to find a way to fill the ListView with the 'synonymable' words and provide links to a DetailView (for synonyms might not yet exist).
I thought about dropping the ListView all together and instead pass a dictionary to a form. By default both the key and the value will be the same word
({'train' : 'train', 'plane' : 'plane'})
and a form would then be generated form this dictionary, allowing users to change the value by having the dictionary value be shown as a text input.
Its a bit more limited (and more cumbersome) than my original plan, but I think this might work. Problem is that I've only really worked with modelforms before, and I am currently stuck on having the form be generated from the dictionary. Could anyone here point me to the right direction?
Regards,
Jasper

Django ManyToMany field using through with relational value

I have a model with many to many field, the field it linked through a table with Boolean value.
I got this in different scenario, lets take user as the main model, interest as the many to many field and user_interest model as the through model, inside user_interest I have a Boolean field which I want user to specify when selecting each of his many to many value,
At the moment am stuck trying to find an appropriate form interface to cater for my need, currently am using MultipleSelect widget which only show selection of interest, is there any example for field control that would allow me to choose the interest and specify the value of the Boolean field? If not, what is best practice to follow in my situation?
Best regards,
Update question with code samples
class Interest(models.Model):
name = models.CharField(_('Name'), max_length=200, )
class UserInterest(models.Model):
interest = models.ForeignKey(Interest, )
user = models.ForeignKey(getattr(settings, 'AUTH_USER_MODEL'), )
join_mail_list = models.BooleanField(default=False)
class User(models.Model):
name = models.CharField(max_length=100)
interest = models.ManyToManyField(Interest, through='UserInterest', related_name='user_interest')
Now based on the models above, when I try to create a ModelForm from User model, I will get interest as MultiSelect field which is perfectly correct. But, in my case, I want the form to collect user interests + for each interest if he wants to join_mail_list for that particular interest. Please advise?
I'll just assume that you want a list of all interests with a checkbox next to them.
Let's assume those models:
class Ressource(models.Model):
name = models.CharField(max_length=255)
class Ressource2Village(models.Model):
ressource = models.ForeignKey(Ressource)
village = models.ForeignKey(Village)
amount = models.FloatField()
class Village(MapObject):
name = models.CharField(max_length=255)
And this form:
class Ressoure2VilllageForm(forms.Form):
name = forms.CharField()
# you would use a BooleanField
value = forms.IntegerField()
Now you can create a formset (https://docs.djangoproject.com/en/1.5/topics/forms/formsets/):
from django.forms.formsets import formset_factory
r2v_formset = formset_factory(Ressoure2VilllageForm)
# use boolean for your case, if you want to edit these values later use n2m to initialize value
# [{'name': r2v.ressource.name, 'value': r2v.value} for r2v in your_village.ressource2village.all()]
init_values = [{'name': r.name, 'value': 0} for r in Ressource.objects.all()]
r2v_formset = r2v_formset(initial=init_values)
Which in your template will give you:
<label for="id_form-0-name">Name:</label><input type="text" name="form-0-name" value="Kohle" id="id_form-0-name">
<label for="id_form-0-value">Value:</label><input type="text" name="form-0-value" value="0" id="id_form-0-value">
<label for="id_form-1-name">Name:</label><input type="text" name="form-1-name" value="Eisenerz" id="id_form-1-name">
<label for="id_form-1-value">Value:</label><input type="text" name="form-1-value" value="0" id="id_form-1-value">
<label for="id_form-2-name">Name:</label><input type="text" name="form-2-name" value="Golderz" id="id_form-2-name">
[...]
The association to the specific user can be done during processing under consideration of his authentication details.
Displaying, validating and processing this should be pretty straightforward, feel free to ask follow up questions.
The question is kind of general, but I'd say that since it's a boolean field, instead of one multiselect field, you have two - one for "Things I'm interested in..." (True) and one for "Thing's I'm not interested in..." (False). Both fields would be multiselect from the same list of items, but the seperation allows the user to apply the boolean field without even noticing. Also, I strongly recommend jQuery UI autocomplete, seems like the best choice here.
Updated answer:
So seeing your code helps clear a few things. The way I would do it is like I suggested in the first place but with a little tweak.
Having a newsletter checkbox for each 'interest' is clumsy, because it requires the user to check the 'interest' twice - once because he's interested in it and once because he wants emails about it. So you have to automatically assume that the user wants to get an email for the subjects he is interested in, but allow them to prevent it if they want.
With that in mind, you build two long text inputs, and implement both of them with a tagging system (here's a good one, here are some more). The first input is "I'm interest in", and the second is "I want to recieve emails about...". Then you add an event listner with jQuery that makes sure that for every tag added to the first input it is automatically added to the second one as well. Then, if the user doesn't want to get emails for a specific subject (or all of them) he can easily take it off the second input (which doesn't have any effect on the first one).
To give you an example, here's a jsfiddle that shows how to make that happen with the chosen-jQuery library. I used Countries and Shipping because I had a pre-made list at the chosen library website, so I just copied it from there. As you can see, it's a pretty simple jQuery code, which you can easily applicate for any other tagging system (and many thanks to the fine Raúl Juárez who helped me work that out myself):
$(".chosen-select").chosen({disable_search_threshold: 10});
$('#countries').on('change', function(e) {
var selected = $(this).val();
$.each(selected, function(index, value) {
$('#shipping option[value="'+value+'"]:first').prop('selected',true);
});
$(".chosen-select").trigger('chosen:updated');
});

Alternative form for Django's `ChoiceField` that can handle thousands of entries

I have a form with a ChoiceField in it. It is rendered to the user as a dropdown box.
Problem is, I have thousands of entries in this field, which is causing the page to (a) load very slowly and (b) be sluggish.
I want an alternative widget, instead of Select, that could handle more than 10,000 choices.
Something like the admin's raw_id_fields would be good (if only it were usable in general forms...) but I'm open to ideas.
If autocomplete is an option for your UI you can take a look to django-simple-autocomplete:
App enabling the use of jQuery UI autocomplete widget for
ModelChoiceFields with minimal configuration required.
EDITED (reply OP comment)
I have not tested this solution, but digging documentation and source it seems that not all data is loaded at a time:
The ability to specify an URL for the widget enables you to hook up to
other more advanced autocomplete query engines if you wish.
Source code:
def get_json(request, token):
"""Return matching results as JSON"""
...
di = {'%s__istartswith' % fieldname: searchtext} # <- look here!
items = queryset.filter(**di).order_by(fieldname)[:10]
Widget source code
$("#id_%(name)s_helper").autocomplete({
source: function(request, response){
$.ajax({ # <-- look here
url: "%(url)s",
data: {q: request.term},
success: function(data) {
I don't know what is the raw_id_fields but why not use a model to store all your choices ?
class Choice(models.Model):
value = models.CharField()
class MyModel(models.Model):
choice = models.ForeignKey(Choice)
It would then be easy to select it if you want to display only 20 at a time for example.
Based on this comment (which really, you should have included in your question):
Let me clarify my task: I have 10,000 users. I have a form in which
you choose a user. You need to be able to choose any user you want.
You can't just load 20, because then you won't be able to choose the
other 9,980 users.
If you want something built-in, you can use the FilteredSelectMultiple widget from django.contrib.admin.widgets, which puts a filter on your select.
You should also cache the results of the 10,000 users so you don't hit your db everytime. This is what is causing your delay, not the number of users (which is tiny, for practical performance problems).

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.

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

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.