Reputation: 6032
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
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!
class DevSkill(models.Model):
dev = models.ForeignKey(UserProfile, related_name = 'skill_set')
skill = models.CharField(max_length = 200)
We changed two things:
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.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
devs = UserProfile.objects.all() # Or W/E queryset would fit.
# Pass context & all.
{% 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 %}
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
-> User
and 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
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
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