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. 🙁
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
We changed two things:
UserProfileForeignKeyinstead of user will simplify the rest of the code. Since you have aUserProfile<->Usermapping anyway, this is not going to be an issue.related_nameattribute so that anyUserProfileobject now has askill_setattribute which store it’s list ofDevSkills.(Please note that
related_nameis not required, and Django will create a genericmodelname_setattribute if you don’t set it).Also,
DevSkillshould 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.In the view:
In the template:
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_relatedandprefetch_relatedfeatures 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 theDevSkill‘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_relatedis a Django 1.4 feature.Footnote: the
UserProfilestuff is going away in Django 1.5, check it out!