0

I think I've read most of the "related/duplicate" questions around (i.e. verbose from template-tag, iterate over field names, iterate through models, and a few, more ...)

Unfortunately I'm afraid I need some hand holding ... as I'm a django n00b and can't seem to make it work. Here is the result I want in the html:

<!-- this in some section of the template -->
First Name : . Joe
Last Name : .. Blogs

<!-- this in another section -->
City:  ....... Xanadu
Country: ..... Mongolia
Occupation: .. Programmer

<!-- this in another section of the template -->
Age: ......... 75 
Height: ...... 180 cm
Eye color:  .. red

With 4 or 5 sections and 10 possible fields each I don't want to just do

{% if data.first_name %}
  First Name:  {{ data.first_name }}
{% endif %}

fourty times! :-/ ... so here's (roughly) my attempt:

model.py

class GenericPerson(models.Model):
    first_name = models.CharField(_('First Name'),
                        max_length = 200, 
                        help_text = _('What is your name?'))

    last_name = models.CharField(_('Last Name'),
                        max_length = 200, 
                        help_text = _('What is your name?'))
    etc, etc ...

    class Meta:
        abstract = True

class Person(GenericPerson):    
    age_in_years = models.PositiveIntegerField(_('Age'), 
                null = True,
                blank = True,
                help_text=_('When was you born?, mate')) 
    location = models.ForeignKey(GeographicalProfile,
                               verbose_name=_('geolocations'),
                               null = True,
                               blank = True)

    height = ... etc, etc.

views.py

...
try:
   person = Person.objects.filter(pk=product_id)
except Person.DoesNotExist:
    raise Http404

generaldata = person.values('first_name',
                              'last_name',
                              etc, etc,
                              )[0]
localdata = person.values('city',
                              'country',
                              etc, etc
                              )[0]
physicaldata = person.values('age',
                                 'height',
                                 'eye_color',
                                  etc, etc
                                 )[0]
extra_context[data] = generaldata
extra_context[loc] = localdata
extra_context[phys] = physicaldata

return render_to_response(template_name, extra_context ,context_instance=RequestContext(request))

using that view, I can

template.html

{% for p in phys.items %}
<ul>
  {% if p.1 %}
     <li>{{p.0}} :  {{p.1}}</li>
  {% endif %}
</ul>
{% endfor %}

for the different sections. But this will return the field as age_in_years, eye_color (instead of the name). Ok, so from the posts above I create a custom template-tag,

from django import template
register = template.Library()

@register.filter
def get_name(object):
    return object._meta.verbose_name # put here whatever you would like to return

register.tag('get_name', get_name)

, load it in the template {% load person_extras %}, now ... I've tried blindly to use this {{something|get_object_name}} everywhere in the template ... but my brute force without understanding gives me:

'str' object has no attribute '_meta'

'list' object has no attribute '_meta'

'tuple' object has no attribute '_meta'

'dict' object has no attribute '_meta'

'ValuesQuerySet' object has no attribute '_meta'

So, where do I use this template tag? or how do I modify my view to make use of this template tag in the for-loop?

Thank you for the insight

Community
  • 1
  • 1
Massagran
  • 1,781
  • 1
  • 20
  • 29

1 Answers1

3

When you call person = Person.objects.filter(pk=product_id) will return a QuerySet of People objects. When you subsequently use values on this queryset it - in the words of the docs -:

Returns a ValuesQuerySet — a QuerySet subclass that returns dictionaries when used as an iterable, rather than model-instance objects.

so basically, in your template when you try to call your template tag, you are trying to call ._meta.verbose_name on a dictionary object instead of a Person object, which won't work.

So, what you could instead do, is just pass the plain queryset to the template, then limit the fields you want to display for each section (general, local, physical) and use some template filters to help you. So something like:

try:
    people = Person.objects.filter(pk=product_id)
except Person.DoesNotExist:
    raise Http404

extra_context = {
    'people' : people,
    'general_fieldnames' : ['first_name', 'last_name' ...],
    'local_fieldnames' : ['age', 'weight' ...],
    'physical_fieldnames' : ['age', 'height' ...], 
}
return render_to_response(template_name, extra_context      
    ,context_instance=RequestContext(request))

There is a problem though (as you know), as there's no easy way to iterate through the fields of an object, or to get their verbose name. Instead though, we can iterate through our physical_fieldnames list, get the corresponding field from the object and also it's verbose name using filters.

from django import template
register = template.Library()

@register.filter
def get_field_value(obj, field_name):
    return getattr(obj, field_name, None)

@register.filter
def get_verbose_name(obj, field_name):
    return obj._meta.get_field(field_name).verbose_name

register.tag('get_verbose_name', get_verbose_name)
register.tag('get_field_value', get_field_value)

and now in the template

{% for person in people %}
<h1>General</h1>
<ul>
{% for field_name in general_fieldnames %}
    {{ person|get_verbose_name:field_name }} : {{ person|get_field_value:field_name }}
{% endfor %}
</ul>
{% endfor %}

...
Massagran
  • 1,781
  • 1
  • 20
  • 29
Timmy O'Mahony
  • 53,000
  • 18
  • 155
  • 177
  • thank you for the clear explanation. It solved a long twisted search! I'll edit your template code to make sure the filter we call in the template has the same name as the one you defined above, and the "object" we filter is the one we loop over. Cheers – Massagran Aug 04 '12 at 16:16