Rico
Rico

Reputation: 6032

Displaying complex dictionaries in Django Templates

I'm using Django 1.4 with Python 2.7 on Ubuntu 12.04.

I have a template that I want to fill with information regarding developers working on a project.

Each developer will have the following information to display:

type
title
first_name
last_name
skills

The trouble I'm running into is that each developer has many skills associated with them.

I've created the model like this:

class DevSkills(models.Model):
    dev = models.ForeignKey(User)

    skill = models.CharField(max_length = 200)

I'm trying to create a view that will populate the dictionary so that I can loop through each developer, display their info, then loop through each skill (to display them one at a time).

Here is the view:

def developers(request):
    """
    ..  function:: developers()

        Provide the page that shows the developer credentials

        :param request: Django Request object
    """
    devs = User.objects.filter(is_staff = True)
    dev_info = {}
    for dev in devs:
        dev_info.update(createDevDisplayDict(dev))
    data = { 'user' : request.user }
    data.update({ 'devs' : dev_info })

    return render_to_response("developers.html", data)

I've designated the is_staff field from User to indicate the user is a developer.

I've created a simple utility that helps me populate the embedded dictionaries so I can loop through them:

def createDevDisplayDict(user):
    """
    ..  function:: createDevDisplayDict()

        Create a dictionary for showcasing the developer

        :param user: developer who we are working with
    """

    userProfile = UserProfile.objects.get(user = user)
    devSkills = DevSkills.objects.filter(dev = user) 
    dev_dict = {}
    user_dict = { 'dev_type'        : userProfile.dev_type,
                  'title'           : userProfile.title,
                  'first_name'      : user.first_name,
                  'last_name'       : user.last_name,
                }
    dev_dict.update(user_dict)

    skill_dict = {}
    for skill in devSkills:
        skill_dict.upate({ 'skill' : skill.skill })

    dev_dict.update(skill_dict)
    return dev_dict

My intention is to loop through each developer, create a "super" dictionary to contain each of their user_dict dictionaries (which are based on their User info) and add to that a dictionary for each of their skills. Then, back in the template I want to loop through the "super" dictionary in such a way that it will present them something like the following:

James Taylor
Project Lead
Software Developer
    • Django
    • Python
    • JavaScript
    • JQuery

Elizabeth Norton
Design Lead
Graphic Designer
    • Edge
    • Adobe Photoshop
    • Adobe Illustrator
    • CSS

Here is the template I'm trying to work with:

{% extends "base.html" %}

{% block content %}
<div>
    <p>Our Developers</p>
</div>
    {% for dev in devs %}
        {{ dev.user_dict.first_name }} {{ dev.user_dict.last_name }}
        {{ dev.user_dict.title }}
        {{ dev.user_dict.dev_type }}

        <ul> 
        {% for skill in dev.skill_dict %}
            <li>skill.skill</li>
        {% endfor %}
        </ul>
    {% endfor %}
{% endblock %}

When I see the page now it looks like this:

Our Developers

Yeah...nothing is getting populated. Any suggestions?

UPDATE 1: I've modified my utility per iMom0's suggestion. I'm now using a list to contain each skill. Like so:

def createDevDisplayDict(user):
    """
    ..  function:: createDevDisplayDict()

        Create a dictionary for showcasing the developer

        :param user: developer who we are working with
    """

    userProfile = UserProfile.objects.get(user = user)
    devSkills = DevSkills.objects.filter(dev = user) 
    dev_dict = {}
    user_dict = { 'dev_type'        : userProfile.dev_type,
                  'title'           : userProfile.title,
                  'first_name'      : user.first_name,
                  'last_name'       : user.last_name,
                }
    dev_dict.update(user_dict)

    skills = []
    for skill in devSkills:
        skills.append(skill.skill)

    skill_dict = {'skill' : skills}

    dev_dict.update(skill_dict)
    return dev_dict

I can see the value in doing this - in fact, it's much more intuitive and I think I was making it too hard the other way. But my template still shows up bare. :(

UPDATE 2:

I know I'm on the write path now. I put some logging in the view:

devs = User.objects.filter(is_staff = True, is_superuser = False)
dev_info = {}
for dev in devs:
    dev_info.update(createDevDisplayDict(dev))

for key in dev_info:
    for sub_key in dev_info[key]:
        logfile.write('{0} = {1}\n'.format(sub_key, dev_info[key][sub_key]))

And the logfile displays:

skills = [u'Java', u'Perl', u'C++', u'Python', u'Django']
dev_type = Software Developer
first_name = Rico
last_name = Cordova
title = Owner

So, it has to be a way I'm calling it in the template, right?

UPDATE 3:

I had a realization that I was disconnecting the user_dict and their skills. So I modified the utility slightly to bring them into a single dictionary.

## Create a logging object
userProfile = UserProfile.objects.get(user = user)
devSkills = DevSkills.objects.filter(dev = user) 
dev_dict = {}
user_dict = { 'dev_type'        : userProfile.dev_type,
              'title'           : userProfile.title,
              'first_name'      : user.first_name,
              'last_name'       : user.last_name,
            }

skills = []
for skill in devSkills:
    skills.append(skill.skill)

user_dict.update({ 'skills' : skills })

dev_dict['user_dict'] = user_dict
return dev_dict

This is a much better solution, in my opinion. I'm still having trouble accessing the user_dict info in the template though. :(

Upvotes: 2

Views: 1085

Answers (3)

Thomas Orozco
Thomas Orozco

Reputation: 55207

You could be using Django's ORM features to make this a lot easier (and, we'll see, get better performance), it's a great feature!

Model code

class DevSkill(models.Model):
    dev     = models.ForeignKey(UserProfile, related_name = 'skill_set')
    skill   = models.CharField(max_length = 200)

We changed two things:

  • Using a UserProfile ForeignKey instead of user will simplify the rest of the code. Since you have a UserProfile <-> User mapping anyway, this is not going to be an issue.
  • We added a related_name attribute so that any UserProfile object now has a skill_set attribute which store it's list of DevSkills.

(Please note that related_name is not required, and Django will create a generic modelname_set attribute if you don't set it). Also, DevSkill should be singular, the object is a single skill!

I also expect that you have the following for UserProfile, and created code assuming you did. You'll need to adapt if you don't.

class UserProfile(models.Model):
    user     = models.OneToOneField(User)
    title    = models.CharField(max_length = 40)
    dev_type = # W/E you want

In the view:

devs = UserProfile.objects.all() # Or W/E queryset would fit.
# Pass context & all.

In the template:

{% extends "base.html" %}

{% block content %}
<div>
    <p>Our Developers</p>
</div>
    {% for dev in devs %}
        {{ dev.user.first_name }} {{ dev.user.last_name }}
        {{ dev.title }}
        {{ dev.dev_type }}

        <ul> 
        {% for skill in dev.skill_set.all %}
            <li>skill.skill</li>
        {% endfor %}
        </ul>
    {% endfor %}
{% endblock %}

Performance

Please be aware that this code (the one you're using now too, though) is going to absolutely kill performance. Indeed, we're doing several queries for each user (Hitting the database for their User and their DevSkills).

That's not a problem though, we can use the ORM's select_related and prefetch_related features to solve that issue:

devs = UserProfile.objects.select_related('user').prefetch_related('skill_set').all()
That way, we only do two queries, one for the UserProfile -> Userand one for the DevSkill's, for which the joining is done in Python, but you shouldn't care about that, Django does it for you.

Please be aware that prefetch_related is a Django 1.4 feature.


Footnote: the UserProfile stuff is going away in Django 1.5, check it out!

Upvotes: 2

Rico
Rico

Reputation: 6032

This is a new concept for me - dict.items in a template. The following is exactly how I was able to display what I wanted.

{% extends "base.html" %}

{% block content %}
<div>
    <p>Our Developers</p>
</div>
    {% for key, value in devs.items %}
        {{ value.first_name }} {{ value.last_name }} <br>
        {{ value.title }} <br>
        {{ value.dev_type }} <br>
        <ul> 
        {% for skill in value.skills %}
            <li>{{ skill }}</li>
        {% endfor %}
        </ul>
    {% endfor %}
{% endblock %}

Upvotes: 0

iMom0
iMom0

Reputation: 12911

dict.update always overwrite the value of dict

In [2]: d = {'key': 'value'}

In [3]: d.update({'key': 'value1'})

In [4]: d
Out[4]: {'key': 'value1'}

Instead, you should use list and list.append.

And your template do not know what user_dict is, correct it,

dev_dict['user_dict'] = user_dict

Upvotes: 1

Related Questions