Show only not None values in Django template - python

I have a Model with numerous attributes; in the template rendered by my DetailView I want to show only the attributes that are not None.
This can be easily done with an if template tag that checks if the value of the attribute is not None, tho I should add an if condition for every attribute in my model, and there's a lot of them.
I would like to iterate through all the attributes and if not None display them.
In pseudo code it would look like this:
{% for attribute in my_instance.all_attributes %}
{% if attribute.value is not None %}
{{attribute.value}}
I can get a tuple of concrete attributes both through the class or the instance:
cls._meta.concrete_fields
or
self._meta.concrete_fields
Now that I have the my_instance.all_attributes in my pseudo code example, I can iterate it but I don't know how to get the actual value of the instance's attribute.
EDIT:
.concrete_values returns an array of Field instances, it looks like this:
(<django.db.models.fields.BooleanField: gov>, <django.db.models.fields.BooleanField: in_group>, <django.db.models.fields.CharField: legal_class>,)
I can access the value of the name attribute of the Field instance using .name. Calling .name on the example above would return 'gov', 'in_group', 'legal_class'

The authors of Django went out of their way to make sure that the template language isn't used to do things like this! Their opinion as I understand it is that templates should do formatting, and Python should do program logic.
There are two ways around it. One is to create a structure that the template can iterate through, and pass it to the DetailView context. Something like
def get_context_data(self):
data = super().get_context_data(**kwargs)
fieldnames = [
x.name for x in self.object._meta.concrete_fields ]
display_fields = [
(name, getattr( self.object, name, None)) for name in fieldnames ]
display_fields = [ x for x in display_fields if x[1] is not None ]
data['display_fields'] = display_fields
return data
and in the template you can now do
{% for name,value in display_fields %}
You might prefer to code a list of names instead of using ._meta.concrete_fields because that lets you choose the order in which they appear. Or you could start with an ordered list and append anything that's in the _meta but not yet in your list (and delete anything that's in your list but not in _meta)
The other way is to use Jinja as the template engine for this view.

Related

In Django how to retrieve substring till ocurrence of a dot, from a model, to show in a template

I have the following model:
class News(models.Model):
news_text = models.TextField(null=True)
... # other fields
with the following view:
def news(r):
news= News.objects
values = {'news':news}
return render(r,'webapp1/news.html',values)
I want to show in the template a substring for the column news_text, till the first 'dot' occurrence, like:
{{news.news_text| split('.')[0] }}
Tried this in template but got:
"invalid filter: 'split'".
Django has a limited set of builtin template filters and there is no split filter.
Also in Django templates you can access object's attributes or methods like {{my_dict.keys}}, but you can't pass any arguments. So you can't do things like {{news.news_text.split('.')}} as well.
All of this is done with intention to force you to separate logic from templates. So for your example probably will be better to define a special context variable and pass it into template's rendering context, like:
def news(r):
news = News.objects.all().get() # don't forget to call some filters on object manager
context = {
'news': news,
'headlines': news.news_text.split('.')[0],
}
return render(r, 'webapp1/news.html', context)
Also note that plural model names may be confusing: is it an array of news in each entry, or not?
Nevertheless you can create custom template tags and filters (and in many cases you should) to solve your problem.

Sort queryset by added field

I'm trying to add an extra field to the instances of my queryset and then sort the set by the new field.
But I get a field error( Cannot resolve keyword 'foo' into field. Choices are: ... )
My view(abstract):
def view(request):
instances = Model.objects.all()
for counter, instance in enumerate(instances):
instance.foo = 'bar' + str(counter)
instances.order_by('foo') #this line causes trouble
return render(request, 'template.html', {'instances': instance})
My template:
{% for instance in instances %}
{{instance.foo}}
{% endfor %}
If I leave out the order_by line the templates renders as expected, so the field seems to be there.
So why do I get a field error?
It would be awesome, if somebody could help me to understand what I'm doing wrong.
Thanks in advance.
I found a possible solution to change the template to
{% for instance in instances|dictsort:'foo' %}
and that works fine, but from what I understand there should be as little logic as possible in the view, so I figure sorting should be done in the view.
Or is this actually the right way?
The Django ORM aims to construct database queries. As a result, you can only query on what a database "knows". Methods, properties, or attributes you added yourself are unknown to the database. The .order_by thus has no effect, since you "patched" the objects in the instances queryset.
If you however call an instances.order_by you construct a new queryset. This queryset takes the context of the parent, and thus represents a (slightly) modified query, but again, a query. Whether the old queryset is already evaluated or patched, is of no importance.
Furthermore even if there was a column foo, it would not help, since the instance.order_by does not order the instance queryset, it constructs a new one, one that looks approximately like the old one, except that the rows are ordered.
You thus will have to sort in Python now. You can for example construct a list of ordered elements with sorted(..), like:
from operator import attrgetter
def view(request):
instances = Model.objects.all()
for counter, instance in enumerate(instances):
instance.foo = 'bar' + str(counter)
mydata = sorted(instances, key=attrgetter('foo'))
return render(request, 'template.html', {'instances': mydata})
So now mydata is no longer a QuerySet, but a vanilla list. Furthermore note that ordering in a database might be slightly different than ordering in Python. In Python exceptions can occur if not all elements have the same type, and furthermore it is not guaranteed that the semantics behind the order relation is exactly the same.
The new attribute in the Python Objects does not exist in the database and only in those instances. order_by changes the queryset and not your current list of objects stored in memory.
One approach would be to use the builtin python sorting functions in the view like: sorted or even list.sort().

Django: avoiding multiple evaluations of the same expression in a template?

When passing an object called widget as part of the context to rendering a django template, I may have a method which is a bit expensive, but I want to display the result of it more than once.
Python:
class Widget:
def work(self):
# Do something expensive
Template
This is a widget, the result of whose work is {{widget.work}}. Do
you want to save {{widget.work}} or discard {{widget.work}}?
Clearly I could work around this by evaluating the method once in the view code, and then passing the result in, but this seems to couple the view and the template too much. Is there a way for the template author to stash values for re-use later in the template? I would like to do something like this:
{% work_result = widget.work %}
This is a widget, the result of whose
work is {{work_result}}. Do you want to save {{work_result}} or discard {{work_result}}?
Does such a construct exist in the django template language?
{% with %}
{% with work_result=widget.work %}
Look Django docs for more information

Object_list always empty

the app is working this way. That i have a simple news adding model as below:
class News(models.Model):
title = models.CharField(max_length=100)
publication_date = models.DateField(auto_now_add=True)
content = models.TextField()
the view
def homepage(request):
posts= News.objects.all() #.get(title="aaa")
return render_to_response('homepage.html', {'a':posts})
and finally the tamplate:
{% for b in a.object_list %}
<li> title:{{ b.title }}</li>
{%empty %}
EMPTY
{% endfor %}
Unfortunately it always sais 'EMPTY'. However if i take the '.get(title="aaa")' option instead of '.all()' (the commented part) I got the right title and content of the message with title 'aaa'.
Can anyone explain what am I doing wrong?
Thanks in advance for Your expertise.
EDIT
I'm sorry I didn't have written the template for the get option Well off course the 'get' verion of template differs. It looks like this:
{{a.title}} {{a.content}
And it works printing the expected title and message content So the 'get' works with the template and the 'for' didn't iterate over the QuerySet returned by all(). I am beginner but object_list is supposed to be the representation for querySet passed in render_on_request as a element of dictionary?
When you use get, the variable posts contains an instance of News. On the other hand, if you use .all(), posts will contain a queryset. So first I would suggest you use filter instead of get, so posts would always be a queryset, and therefore you wouldn't have such an inconsistent behaviour ...
When you want to iterate over something like this:
for object in object_list:
print object
object_list needs to support iterating. list, tuple, dict, and other types support that. You can define your own iterator class by giving it a iter method. See the docs for that.
Now, in your example
return render_to_response('homepage.html', {'a':posts})
posts is a Queryset instance that supports iterating. Think of it this way:
{% for b in News.objects.all %}
this is what you would like to have, but what you actually did is this:
{% for b in News.objects.all.object_list %}
But News.objects.all does not have an object_list attribute!
News.objects.all is what your object_list should be, so just write:
{% for b in a %}
Please post the exact code you are running. There is no way that either of your alternatives would work with a.object_list, because there is no definition of object_list anywhere and it's not a built-in Django property.
And assuming you actually mean that for b in a doesn't work in the first code but does in the second, this is not true either, because with .get you won't have anything to iterate through with for.
However, let's assume what you actually did was pass the results of .all() to the template, and the template didn't have the for loop. That wouldn't work, because all() - like filter() - returns a QuerySet, which must be iterated through. For the same reason, get() wouldn't work with a for loop.
Edited after comment "object_list is supposed to be the representation for querySet passed in render_on_request " - no, it isn't. Where did you get that idea? If you pass a queryset called a to the template, then you iterate through a, nothing else. object_list is the name that is used by default in generic views for the queryset itself - ie what you have called a - but in your own views you call it what you like, and use it with the name you have given it.
Edited after second comment I don't know why this should be confusing. You've invented a need for object_list where there is no such variable, and no need for one. Just do as I said originally - {% for b in a %}.

Dynamically Display field values in Django template (object.x)

I am currently working on an app that uses custom annotate querysets. Currently i have 2 urls setup, but i would need one for each field that the users would like to summarize data for. This could be configured manually, but it would violate DRY! I would basically have +-8 urls that basically do the same thing.
So here is what i did,
I have a created custom model manager
I have a view
I have the URLS configured
All of the above works.
So basically the URL config passes to the view the name of the field to annotate by (group by for SQL folks), the view does some additional processing and runs the custom model manager based on the field that was passed to it.
The URL looks like this:
url('^(?P<field>[\w-]+)/(?P<year>\d{4})/(?P<month>\d+)/(?P<day>\d+)/$','by_subtype', name='chart_link'),
The field is the column in db the that is used when the queryset is actually run. It is passed from the view, to my custom manager. Below is an example of the code from the manager:
return self.filter(start_date_time__year=year).filter(start_date_time__month=month).filter(start_date_time__day=day).values(field).annotate(Count(field))
In addition, i pass the value of field as context variable. This is used to dynamically build the links. However the problem is actually looping through the query set and displaying the data.
So your typical template code looks like this:
{% for object in object_list %}
{{ object.sub_type }} : {{ object.sub_type__count|intcomma }}
{% endfor %}
Basically you have to hard code the field to diplay (i.e object.x), is there anyway to dynamically assign this? i.e
if field = business
then in the template it should automatically process:
{{ object.business }}
Can this be done? Or would i need to create several URLS? Or is there a better way to achieve the same result, a single view and url handling queries dynamically.
You can find the code over at github, the template part is now working using this snippet: http://www.djangosnippets.org/snippets/1412/ So if you come across this later and want to do something similar have a look at the code snippet at github. : http://gist.github.com/233262
It sounds like you want to do something along the lines of:
# in the views.py:
field = 'business'
{# in the template: #}
{{ object.field }}
and have the value of object.business appear in the output. This isn't possible with the Django template language out of the box.
There are snippets that define template filters you can use to accomplish this though: http://www.djangosnippets.org/snippets/1412/
As mentioned above, you can do this with a custom template filter.
For example:
#register.filter(name='get_attr')
def get_attr(obj, field_name):
if isinstance(obj, dict):
return obj.get(field_name)
return getattr(obj, field_name)
Then, using it in your template:
{{ obj|get_attr:'business' }}

Categories

Resources