Strategy for handling recursive template display in Django - python

I'm rewriting a family tree app in Django that I previously wrote in Laravel. There's an 'outline view' page where I start with the 'original families' at the top of the tree, and show an outline of their children, their children's families/kids, those kids' families/kids, etc.
First I've generated a big nested list with all the info (in the controller for laravel, and in views.py in my Django rewrite). Then I give that list to a couple of views to display the lists for each original family. (Code included below)
Testing the logic a couple levels at a time it works, but when I let it call recursively and load my outline page, it gives me a recursion error: maximum recursion depth exceeded while calling a Python object.
Reading around, it sounds like python has a known limitation that it doesn't handle tail recursion, and I saw some posts saying that showing recursive views isn't good practice in Django.
Update: I've also started down the road of making a management command to generate an html page for this (that I can just show statically, and regenerate when I add new people), and that way I do avoid the recursion error. I'll do a bit more debugging with the original views to see what I can find out about where/when I reach the error (only for one particular branch or family, etc?) in case there's something I can fix in the way I generate the list.
Is there a better way to approach this problem in Django?
outline_whole.html:
{% for entry in results %}
{% get_class entry as object_type %}
{% include chunk_view %}
{% endfor %}
outline_family_chunk.html: (aka chunk_view, passed into the context)
{% get_class entry as object_type %}
<ul>
{% if object_type == 'Family' %}
{{ entry.display_name }}
{% elif object_type == 'Person' %}
<li>{% include "familytree/person_name_link.html" with person=entry %}</li>
{% elif object_type == 'list' %}
<ul>
{% for item in entry %}
{% include chunk_view with entry=item %}
{% endfor %}
</ul>
{% endif %}
</ul>
Example of the list it's working through- it gives a family with its children, then a list for the next family (where one of the original children was a parent):
[<Family: The Skywalker family (Anakin & Padme)>, <Person: Luke Skywalker>, <Person: Leia Skywalker>,[<Family: The Solo family >, <Person: Kylo Ren>]]

So without data it's hard to see where the problem is, but the obvious candidate is:
this_user -> Branch -> descendants[] --\
^ | <- has this_user
|______<___________<__________<______/
So when rendering a person, you render his branch, for which you call get_descendants and then from what I can tell, this could include the top-level user and we go round and round.
But like I said, it's a guess.

Related

How to access a list's length in a template's code block for Django?

In a template, I have a variable of type list called "participants". I want to check if the length of the list is equal to 2, for example. I tried the following:
{{ participants | json_script:"participants"}}
{% if participants|length==2 %}
.....
{% endif %}
However, this does not work. The error I get is:
TemplateSyntaxError at /chat/lobby/
Could not parse the remainder: '==2' from 'participants.count==2'
Can someone point out a way to access a list's length in a template's code block? thank you for your time and consideration!
The problem is the (lack of) spacing around the == part. If you rewrite this to
{% if participants|length == 2 %}
…
{% endif %}
the template parser will no longer error.
That being said, a template is used for rendering logic, and while it is hard to tell, it looks that this is more business logic, which, as #MeL says belongs in the views.

How to get minion id from jinja script inside a salt state

I need to apply a if-else logic based on a static minion id inside a state file. The target glob qualifies a whole bunch of servers, but I need to run a small piece of logic on a single server and run a bunch of common things on all of them. How can I do this?
When I put this in a Jinja file, its errors:
{% import salt.config %}
{% minion_opts = salt.config.minion_config('/etc/salt/minion') %}
{% print(minion_opts['id']) %}
{% if minion_opts['id'] == 'xyz.server': %}
-- run the logic here
..
..
Error:
- Rendering SLS 'base:bin.test' failed: Jinja syntax error: expected token 'as', got 'end of statement block'; line 1
-
- ---
- {% import salt.config %} <======================
- {% minion_opts = salt.config.minion_config('/etc/salt/minion') %}
- {% print(minion_opts['id']) %}
It probably goes without saying I am not a Saltstack expert by any means.
So first off, I realize this is an old question, but it still shows up at the top of the my search results for both DDG and Google for a somewhat related problem and the current answers don't really deliver on the ask, so here's an answer based off the latest SaltStack version (3002):
In order to get control flow working for Jinja based off minion id:
{% if salt['grains.get']('id') == 'minion_a' %}
some text to render for minion_a
{% else %}
some text to render otherwise
{% endif %}
I realize there were other issues in how the question was phrased and for the sample code provided, but my code snippet answers the actual question of:
I need to apply a if-else logic based on a static minion id inside a state file.
How can I do this?
The error occurs because you're using Jinja's import in an unexpected way. If this is a Flask app, you should use something like this in your views.py route to provide variables to the template:
render_template('my_template.html', salt=salt)
The corrected code should look something like this:
{% set minion_opts = salt.config.minion_config('/etc/salt/minion') %}
{{ minion_opts['id'] }}
{% if minion_opts['id'] == 'xyz.server' %}
{{ 'logic goes here' }}
{% endif %}
See the Assignments docs on how to assign values to variables.
No colon is necessary at the end of the if statement, and remember to use {% endif %} after you're done with your conditional statements.

How to add pages to navigation on Pelican?

I know this may not be a very smart question, but it really bother me for a while.
I am using Pelican for site generating, with theme SoMA/SoMA2. I am adding a folder under content and some pages inside. This should appear on the navigation according to the Tutorial, but didn't.
I tried with default theme (in quickstart) and it works. Does it mean SoMA/SoMA2 don't support personalized navigation? Any ways I can do with it?
Thanks.
Your intuition is correct: the SoMA/SoMA2 themes do not currently add pages to the navigation menu. You can see this by comparing the notmyidea base template with the SoMA base template. The relevant code is:
{% if DISPLAY_PAGES_ON_MENU -%}
{% for pg in PAGES %}
<li{% if pg == page %} class="active"{% endif %}>{{ pg.title }}</li>
{% endfor %}
{% endif %}
I can't guarantee that will be entirely sufficient, of course, since I haven't fully examined how SoMA implements its navigation, CSS, et cetera. But perhaps this might point you in the right direction.

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?

Sorting and indexing into a list in a Django template?

How can you perform complex sorting on an object before passing it to the template? For example, here is my view:
#login_required
def overview(request):
physicians = PhysicianGroup.objects.get(pk=physician_group).physicians
for physician in physicians.all():
physician.service_patients.order_by('bed__room__unit', 'bed__room__order', 'bed__order')
return render_to_response('hospitalists/overview.html', RequestContext(request, {'physicians': physicians,}))
The physicians object is not ordered correctly in the template. Why not?
Additionally, how do you index into a list inside the template? For example, (this doesn't work):
{% for note_type in note_types %}
<div><h3>{{ note_type }}</h3>
{% for notes in note_sets.index(parent.forloop.counter0) %}
#only want to display the notes of this note_type!
{% for note in notes %}
<p>{{ note }}</p>
{% endfor %}
{% endfor %}
</div>
{% endfor %}
Thanks a bunch, Pete
As others have indicated, both of your problems are best solved outside the template -- either in the models, or in the view. One strategy would be to add helper methods to the relevant classes.
Getting a sorted list of a physician's patients:
class Physician(Model):
...
def sorted_patients(self):
return self.patients.order_by('bed__room__unit',
'bed__room__order',
'bed__order')
And in the template, use physician.sorted_patients rather than physician.patients.
For the "display the notes of this note_type", it sounds like you might want a notes method for the note_type class. From your description I'm not sure if this is a model class or not, but the principle is the same:
class NoteType:
...
def notes(self):
return <calculate note set>
And then the template:
{% for note_type in note_types %}
<div><h3>{{ note_type }}</h3></div>
{% for note in note_type.notes %}
<p>{{ note }}</p>
{% endfor %}
</div>
{% endfor %}
"I'd like to do this from within a template:"
Don't. Do it in the view function where it belongs.
Since the question is incomplete, it's impossible to guess at the data model and provide the exact solution.
results= physician.patients.order_by('bed__room__unit', 'bed__room__order', 'bed__order')
Should be sufficient. Provide results to the template for rendering. It's in the proper order.
If this isn't sorting properly (perhaps because of some model subtletly) then you always have this kind of alternative.
def by_unit_room_bed( patient ):
return patient.bed.room.unit, patient.bed.room.order, patient.bed.order
patient_list = list( physician.patients )
patient_list.sort( key=by_unit_room_bed )
Provide patient_list to the template for rendering. It's in the proper order.
"how do you index into a list inside the template"
I'm not sure what you're trying to do, but most of the time, the answer is "Don't". Do it in the view function.
The template just iterate through simple lists filling in simple HTML templates.
If it seems too complex for a template, it is. Keep the template simple -- it's only presentation. The processing goes in the view function
You should be able to construct the ordered query set in your view and pass it to your template:
def myview(request):
patients = Physician.patients.order_by('bed__room__unit',
'bed__room__order',
'bed__order')
return render_to_response('some_template.html',
dict(patients=patients),
mimetype='text/html')
Your template can then loop over patients which will contain the ordered results. Does this not work for you?
EDIT: For indexing, just use the dot syntax: mylist.3 in a template becomes mylist[3] in python. See http://docs.djangoproject.com/en/dev/ref/templates/api/#rendering-a-context for more information.
This is one way of doing it, although very ugly :
{% for note in note_sets|slice:"forloop.counter0"|first %}

Categories

Resources