django-table2 multi-column sorting UI - python

I am trying to do multi-column sorting in django-tables2.
I can add ?sort=date&sort=job_number
to the end of my url and it will sort by date, then job number.
But when a user clicks a column heading, it will replace the current sort querystring with the new one! Is there a way to more elegantly expose multi-column sort to the end users?
I am using the 'querystring' tag from django-tables2, but as stated above, it rewrites the value instead of appending it.

Okay, I have worked out a solution that works, but it isn't quite perfect, so if anyone wants to propose something better, I'm all ears!
First, I created a new templatetag (see https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/ for details about where to put a custom templatetag)
from django import template
from django_tables2.templatetags.django_tables2 import querystring
register = template.Library()
#register.inclusion_tag('django_tables2/header.html', takes_context=True)
def render_header(context):
for column in context['table'].columns:
column.sort_existing = False
if 'sort' in context['request'].GET:
if column.name in context['request'].GET['sort']:
column.sort_existing = True
return context
Then, I created a custom template called django_tables2/header.html for that tag to use:
{% load querystring from django_tables2 %}
<thead>
<tr>
{% for column in table.columns %}
{% if column.orderable %}
{% if column.sort_existing %}
<th {{ column.attrs.th.as_html }}><a href='{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}'>{{ column.header }}</a></th>
{% else %}
<th {{ column.attrs.th.as_html }}><a href='{% querystring %}&{{ table.prefixed_order_by_field }}={{ column.order_by_alias }}'>{{ column.header }}</a></th>
{% endif %}
{% else %}
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
And Finally, I altered my django_tables2/table.html template to use my custom templatetag to render the table header, replace the table.thead block with:
{% block table.thead %}
{% render_header %}
{% endblock table.thead %}
And that should do the trick! Clicking on multiple column headers will sort them in the order clicked, clicking on a the same one twice will clear previous selections (and reverse the order). It isn't perfect. Perhaps I'll improve upon it later, but it works for my immediate use case.
Perhaps I will reach out to the django_tables2 project to see if they are interested in including my custom template tag into the main project :)
EDIT: I should note, this requires 'django.core.context_processors.request' or equivalent in your context processors in your settings.
EDIT: Fixed table.html to correct code. Also, see https://github.com/bradleyayers/django-tables2/issues/223 to track this issue.

It looks like this is not an option for django-tables2 at the moment, though it is noted as an issue on the git page. I would also like to see this as on option for this package as well.
Looking through the source code briefly in the table.html template the href for the column header is produced by the following code: {% querystring table.prefixed_order_by_field=column.order_by_alias.next %}. Digging a little deeper it appears that there is an OrderByTuple object, that seems to want to perform this function, but never gets passed to the rendering. I haven't dug down enough to figure out why this OrderByTuple is not passed to the href, but may have something to do with the fact the the OrderBy called from the template referenced above is linked to the column and not the table. Sorry I couldn't acutually come up with a solution, but hope this helps a little.

Related

How do I submit multiple forms with a single submit button in django?

I have managed to create the forms I need using modelformset_factory.
avaluos = Avaluo.objects.filter(Estatus__contains='CONCLUIDO',Factura__isnull=True)
FacturaFormset = modelformset_factory(Avaluo,form=FacturaForm,extra=0)
Currently this is generating the following HTML for each of the rows found:
<form id="id-FacturaForm" class="blueForms" method="post">[..]</form>
<form id="id-FacturaForm" class="blueForms" method="post">[..]</form>
<form id="id-FacturaForm" class="blueForms" method="post">[..]</form>
I want to submit all the forms using a single submit button.
Any ideas?
UPDATE
I ended up using django-crispy-forms which allowed me to gerate inputs for each row, and then I just manually added the form and submit.
self.helper.form_tag = False
{{example_formset.management_form }}
{% for a,b in olist %}
{{ b.id }}
<tr>
<td style="width:10px;"> {% crispy b %} </td>
<td> {{a.id}} </td>
</tr>
{% endfor %}
Read more into model formsets. You don't need to have separate form tags, it's the whole point of using a formset.
<form method="post" action="">
{{ factura_formset.management_form }}
<table>
{% for form in factura_formset %}
{{ form }}
{% endfor %}
</table>
</form>
Also, every time you use the id attribute more than once on a pageā€¦ a developer cries themselves to sleep somewhere in the world.
I suspect you will need to do it using Ajax - otherwise as soon as one form is submitted you will not be able to go the other way.
There are a few jQuery form libraries that should make it relatively straightforward. For example, http://malsup.com/jquery/form/.
It would look something like:
$('#button-id').click(function() {
$('.blueForms').ajaxSubmit();
});
Of course, you'll then need to deal with error handling and waiting for all the forms to have submitted.
If you're trying to create many instances of the "same" form (this is, they all look equal), as if it were one of many childs belonging to a single, master element, you don't actually need to create a form tag for each of the formsets.
If I'm not mistaken, you're trying to edit many facturas for a single avaluo object. Am I right? The representation would be a single "avaluo" form with many inline formsets, one for each "factura".
Check out the inline formsets factory instead of the modelformset factory.

django philosophy: when to include templates and when to have code generate html?

When using Django templates, should I have some templates that act like "subroutines", so to speak, or should I generate HTML from within my code in these cases?
For example, I have a template with several lists of names, each of which I want to turn into a select. Should I have a template that renders the name_list variable into a select, and do something like this:
#in the view:
return {'name_list_1': name_list_1,
'name_list_2': name_list_2,
'name_list_3': name_list_3}
#in the template:
{% with name_list_1 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
{% with name_list_2 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
{% with name_list_3 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
Or should I have a function in my code, name_list_to_select_html, which does the same job, and do this:
return {'name_list_1_html': name_list_to_select_html(name_list_1),
'name_list_2_html': name_list_to_select_html(name_list_2),
'name_list_3_html': name_list_to_select_html(name_list_3)}
#in the template:
{{ name_list_1_html|safe }}
{{ name_list_2_html|safe }}
{{ name_list_3_html|safe }}
Or are both of these wrong and I am getting the philosophy totally wrong?
Additional question: in terms of speed, is it slow to constantly include templates? Is that a bonus point for the in-code html generation?
Generally, HTML should only be generated in the templating system or directly related code. That keeps the view of the data completely separate from the business and functional logic. I feel that's a proper separation of concerns. Go with your first solution.
As for performance, Django should probably take around the same amount of time running either code. But it has built-in view and template fragment caching if you know those segments of code don't need to be regenerated on every request.

Jinja's loop variable is not available in include-d templates

I have code similar to the following in one of my jinja template
{% for post in posts %}
{% include ["posts/" + post.type + ".html", "posts/default.html"] %}
{% endfor %}
which is supposed to render each post inside the posts collection, depending on the .type of the post. I have a different template setup for each post.type. And for those I don't have a template, it reverts to the default post template.
Now, I want the index of the post being displayed from bottom, inside the post templates, which is provided by loop.revindex. But for some reason, if I use loop.revindex inside the post template, I get a error saying UndefinedError: 'loop' is undefined.
So, is loop not available in the included templates? Is this by design? Am I doing something wrong with how I organised my templates for this to be not available?
Edit Okay, I came up with a workaround, in the for loop, before I include my template, I do
{% set post_index = loop.revindex %}
and use post_index inside the post template. Not ideal, but seems like the only way. I still want to know your solutions though.
Edit 2 One other thing, I am able to access the post variable inside the included template, but not the loop variable.
If might be possible with the {% with %} statement.
Try this:
{% with %}
{% set loop_revindex = loop.revindex %}
{% include ... %}
{% endwith %}
Instead of using loop.revindex in the included template, use loop_revindex.
Another option is to pass the entire loop variable into the included template by setting a local variable to loop
{% for post in posts %}
{% set post_loop = loop %}
{% include ["posts/" + post.type + ".html", "posts/default.html"] %}
{% endfor %}
This gives you access to all of the loops properties, and, to me, makes it more clear in the included template what the variable is.

Sorting related items in a Django template

Is it possible to sort a set of related items in a DJango template?
That is: this code (with HTML tags omitted for clarity):
{% for event in eventsCollection %}
{{ event.location }}
{% for attendee in event.attendee_set.all %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
displays almost exactly want I want. The only thing I want to change is I the list of attendees to be sorted by last name. I've tried saying something like this:
{% for event in events %}
{{ event.location }}
{% for attendee in event.attendee_set.order_by__last_name %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
Alas, the above syntax doesn't work (it produces an empty list) and neither does any other variation I have thought of (lot's of syntax errors reported, but no joy).
I could, of course, produce some kind of array of sorted attendee lists in my view, but that is an ugly and fragile (and did I mention ugly) solution.
Needless to say, but I'll say it anyway, I have perused the on-line docs and searched Stack Overflow and the archives of django-user without finding anything helpful (ah, if only a query set were a dictionary dictsort would do the job, but it's not and it doesn't)
==============================================
Edited to add additional thoughts
after accepting Tawmas's answer.
Tawmas addressed the issue exactly as I presented it -- although the solution was not what I expected. As a result I learned a useful technique that can be used in other situations as well.
Tom's answer proposed an approach I had already mentioned in my OP and tentatively rejected as being "ugly".
The "ugly" was a gut reaction, and I wanted to clarify what was wrong with it. In doing so I realized that the reason it was an ugly approach was because I was hung up on the idea of passing a query set to the template to be rendered. If I relax that requirement, there is an un-ugly approach that should work.
I haven't tried this yet, but suppose that rather than passing the queryset, the view code iterated through the query set producing a list of Events, then decorated each Event with a query set for the corresponding attendees which WAS sorted (or filtered, or whatever) in the desired way. Something like so:
eventCollection = []
events = Event.object.[filtered and sorted to taste]
for event in events:
event.attendee_list = event.attendee_set.[filtered and sorted to taste]
eventCollection.append(event)
Now the template becomes:
{% for event in events %}
{{ event.location }}
{% for attendee in event.attendee_list %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
The downside is the view has to "actualize" all of the events at once which could be a problem if there were large numbers of events. Of course one could add pagination, but that complicates the view considerably.
The upside is the "prepare the data to be displayed" code is in the view where it belongs letting the template focus on formatting the data provided by the view for display. This is right and proper.
So my plan is to use Tawmas' technique for large tables and the above technique for small
tables, with the definition of large and small left to the reader (grin.)
You can use template filter dictsort https://docs.djangoproject.com/en/dev/ref/templates/builtins/#std:templatefilter-dictsort
This should work:
{% for event in eventsCollection %}
{{ event.location }}
{% for attendee in event.attendee_set.all|dictsort:"last_name" %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
You need to specify the ordering in the attendee model, like this. For example (assuming your model class is named Attendee):
class Attendee(models.Model):
class Meta:
ordering = ['last_name']
See the manual for further reference.
EDIT. Another solution is to add a property to your Event model, that you can access from your template:
class Event(models.Model):
# ...
#property
def sorted_attendee_set(self):
return self.attendee_set.order_by('last_name')
You could define more of these as you need them...
One solution is to make a custom templatag:
#register.filter
def order_by(queryset, args):
args = [x.strip() for x in args.split(',')]
return queryset.order_by(*args)
use like this:
{% for image in instance.folder.files|order_by:"original_filename" %}
...
{% endfor %}
regroup should be able to do what you want, but is there a reason you can't order them the way you want back in the view?

In Django, where is the best place to put short snippets of HTML-formatted data?

This question is related to (but perhaps not quite the same as):
Does Django have HTML helpers?
My problem is this: In Django, I am constantly reproducing the basic formatting for low-level database objects. Here's an example:
I have two classes, Person and Address. There are multiple Addresses for each Person, setup likeso (in their respective models.py)
class Person(models.Model):
...
class Address(models.Model):
contact = models.ForeignKey(Person)
Now, whenever I look at a Person, I want to see all their Addresses. So suppose Persons/views.py has something likeso:
def detail(request, person_id):
person = get_object_or_404( Person, pk=person_id )
return render_to_response('persons/details.html',
{ 'title' : unicode(person), 'addresses': person.address_set.all() } )
And, I have a template, persons/details.html, with code, for example, like-so:
{% extends "base.html" %}
{% for address in addresses %}
<b>{{ address.name }}</b>
{{ address.type }} <br>
{{ address.street_1 }}<br>
{{ address.street_2 }}<br>
{{ address.city }} {{ address.stateprov }} {{ address.postalcode }}<br>
{{ address.country }}
<hr>
{{ endfor }}
I am repeating this code quite a bit, often with minor variations, such when it's in a table, and then < br > must be substituted by < /td >< td >. Other times, I don't want a street_2 to display (or the < br > after it). All to say, there is fundamental logic that I want to express, that I am even more loath to tote around with block-and-copy!
What I want is a persons/details.html with, for example, the following:
{% extends "base.html" %}
{% for address in addresses %}
{% address.as_html4 %}
{% endfor %}
And if I want inline table, something likeso (I guess!):
{% extends "base.html" %}
<table><tr>
{% for address in addresses %}
<tr><td> {% address.as_html4 </td><td> %} </td></tr>
{% endfor %}
</table>
The question is, then: Where is the best place to put the formatting? The logic?
Django seem to have the following (plausible) options:
Put the formatting in models.py
Put the logic/formatting in views.py
Put the logic/formatting in some other sub-class of Person or Address (i.e. addresses/html4.py)
Create custom tags
Help / insight much appreciated!
Sounds like an inclusion tag is what you're looking for. You could have a template and tag for each major variation and use the tag's arguments to customise the context for each template as required.
Basic tag definition:
#register.inclusion_tag('person/address.html')
def display_address(address):
return {'address': address}
Use in templates (assuming the templatetag module containing it has already been {% load %}-ed):
{% display_address address %}
I would use a template tag outputting data using a template html-file a k a inclusion-tag
I think template filter will be useful too. You can pass filter on each object, for example:
{{ value|linebreaks }} # standard django filter
Will produce:
If value is Joel\nis a slug, the output will be <p>Joel<br>is a slug</p>.
See Django Built-in template tags and filters complete reference.

Categories

Resources