Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Django: in view is it better to add properties to an object or make a dictionary of the data?

My models don't really matter in this case, this is a fundamental Python question, I suppose.

Say I have a queryset of items and I want to calculate some things for each one to be displayed in a template.

In my view, I can create a list of objects, and for each object I can set a property on that object for the calculation, then I can display that in the template. OR I can create a list of dictionaries and only get the fields I need to display in each dictionary along with the calculated field. Which is better for performance, and in general practice?

An overly-simplified example for clarity (I know I can call getAge() from the template, what I am really calculated is more complex and for performance I want to do the calculations in the view code):

models.py:

class Person(models.Model):
    first_name = ...
    last_name = ...
    date_of_birth = ...
    .
    .
    .
    def getAge(self):
        return ... # return the calculated years since date_of_birth

views.py:

def method1_object_property(request):
    people = Person.objects.all()
    for p in people:
        p.age = p.getAge()
    return render_to_response('template.htm', {'people': people})

def method2_dictionary(request):
    people = Person.objects.all()
    data = list()
    for p in people:
        row = dict()
        row['first_name'] = p.first_name
        row['last_name'] = p.last_name
        row['age'] = p.getAge()
        data.append(row)
    return render_to_response('template.htm', {'people': data})

template.htm:

<ul>
    {% for p in people %}
        {{ p.first_name }} {{ p.last_name }} (Age: {{ p.age }})
    {% endfor %}
</ul>

Both methods work just fine so far as I can tell, I was just curious what the preferred method would be and why. Are there performance issues assigning new fields dynamically to an existing object in memory using the object dot property method (object.new_field = 'some_detail')?

UPDATE:

Yes, I know in my example I can call getAge() from template, and yes, this is the incorrect naming standard for methods which should be lowercase with underscores. I think my example is too simple and is clouding what I really want to know.

What is the best way to add information to an object that I want displayed in the view that is not a part of the model layer. Say I get a QuerySet of Person objects and want to calculate how many times they have logged into my website in the last 30, 60 and 90 days. I want to create three "properties" for each Person object on the fly. I can set this in the view with

for p in people:
    p.last_30 = Login.objects.filter(person=p, login_date__gt=date.today()-timedelta(days=30))
    p.last_60 = Login.objects.filter(person=p, login_date__gt=date.today()-timedelta(days=60))
    p.last_90 = Login.objects.filter(person=p, login_date__gt=date.today()-timedelta(days=90))

Then in my template I can display those "properties." I just wanted to make sure I'm not violating some Python standard or cheating the system. Alternately, I could store these other lookups in a dictionary with the object in one key/pair, and the various details in separate ones. This is a bit more work in the view, but I was curious if it is better for performance or compliance of standards to do so?

Sorry if my original question was not clear enough, or my example added confusion.

like image 544
Furbeenator Avatar asked Dec 26 '13 22:12

Furbeenator


People also ask

What is the purpose of Django views?

Django views are Python functions that takes http requests and returns http response, like HTML documents. A web page that uses Django is full of views with different tasks and missions. Views are usually put in a file called views.py located on your app's folder.

What kind of data is passed into a view in the request object?

The request object has view input field values in name/value pairs. When we create a submit button then the request type POST is created and calls the POST method. We have four data, those are in Name-Value pairs.

Which view can be used to show the details of an object in Django?

Detail View refers to a view (logic) to display one instances of a table in the database.

What is the purpose of __ Str__ method in Django?

str function in a django model returns a string that is exactly rendered as the display name of instances for that model. # Create your models here. This will display the objects as something always in the admin interface.


1 Answers

Definitely method 1.

Method 2 is pointless, you can iterate over the queryset directly in the template, there is no need to build up an intermediate 'list of dicts' in your view. eg you just can do:

def method2_dictionary(request):
    people = Person.objects.all()
    return render_to_response('template.htm', {'people': people})

in your template:

{% for p in people %}
    {{ p.first_name }}
    etc
{% endfor %}

Coming back to method 1...

This: p.age = p.getAge() is also pointless, you can directly call the method in your template as {{ p.getAge }} (as long as your method does not take arguments) see the docs here:
https://docs.djangoproject.com/en/dev/topics/templates/#accessing-method-calls

Note that in Python we generally prefer to use 'lower-case with underscores' for method names, eg def get_age(self) and {{ p.get_age }}
(see the official 'PEP8' style guide for Python here http://www.python.org/dev/peps/pep-0008/#function-names)

If your get_age method has no side-effects and takes no arguments you may like to make it a property which is Python's way of having a getter method you can access without the parentheses.

In this case it would make sense to name it just age:

@property
def age(self):
    return ... # return the calculated years since date_of_birth

and in your template:

{% for p in people %}
    {{ p.first_name }}
    {{ p.age }}
    etc
{% endfor %}

For more info about Python properties, see here:
http://docs.python.org/2/library/functions.html#property

Some more info in this SO question:
Real world example about how to use property feature in python?

UPDATE

Referring to your updated question... as a question of style I would still make these (last_30 etc) methods on the model rather than adding ad hoc properties onto each model instance in the view code.

From a performance perspective, the difference in memory, processing time etc of method lookup vs dictionaries etc is trivial in most real world situations ...by far the biggest performance consideration in this kind of code is usually the number of database queries.

If you know you're going to do an extra query (or three) for each item in your queryset it's worth looking for ways to get everything in one or more big queries.

In some cases you may be able to use the annotate() method:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate

(I don't think that's possible in your example)

In your specific code above you only need to query for the 90 days (oldest interval) and you could filter the 60 and 30 days sets from that in Python without querying the db again.

But that would still be doing one extra query per item in your people queryset. It'd be better to do one big query for the Login objects for all (or whatever subset) of the people. Since there is a foreign key relation for Person on Login we can use select_related() to get the Person instances in one big query when we query Login model:

def method3(request):
    logins = Login.objects.filter(
        person__in=Person.objects.all(),
        login_date__gt=date.today() - timedelta(days=90)
    ).order_by('person', 'login_date').select_related()
    return render_to_response('template.htm', {'logins': logins})

Note if you're really doing Person.objects.all() you wouldn't need the person__in filter above, only if you wanted to filter the Person set in some way.

Now that we got all the data in one big query we can do what we need to on the python side to display the data. eg in the template we could use the regroup tag:

{% regroup logins by person as people %}
{% for person in people %}
    {% with person.grouper as p %}
        {{ p.first_name }}
        {% for login in person.list %}
            {{ login.login_date }}
        {% endfor %}
    {% endwith %}
{% endfor %}

You could take this further and write a custom tag for the login date ranges... I won't detail that here but in your template it could look something like:

{% regroup logins by person as people %}
{% for person in people %}
    {% with person.grouper as p %}
        {{ p.first_name }}
        {% logins_since person.list 60 as last_60_days %}
        {% logins_since person.list 30 as last_30_days %}
        {% for login in last_30_days %}
            {{ login.login_date }}
        {% endfor %}
        {% for login in last_60_days %}
            {{ login.login_date }}
        {% endfor %}
    {% endwith %}
{% endfor %}
like image 84
Anentropic Avatar answered Sep 29 '22 11:09

Anentropic