Отображение сложных словарей в шаблонах Django

Я использую Django 1.4 с Python 2.7 в Ubuntu 12.04.

У меня есть шаблон, который я хочу заполнить информацией о разработчиках, работающих над проектом.

Каждый разработчик будет иметь следующую информацию для отображения:

type
title
first_name
last_name
skills

Проблема, с которой я сталкиваюсь, заключается в том, что у каждого разработчика есть много связанных с ним навыков.

Я создал модель следующим образом:

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

    skill = models.CharField(max_length = 200)

Я пытаюсь создать представление, которое будет заполнять словарь, чтобы я мог перебирать каждого разработчика, отображать их информацию, а затем перебирать каждый навык (чтобы отображать их по одному).

Вот вид:

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)

Я назначил поле is_staff из User, чтобы указать, что пользователь является разработчиком.

Я создал простую утилиту, которая помогает мне заполнять встроенные словари, чтобы я мог просматривать их в цикле:

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

Я намерен пройтись по каждому разработчику, создать «супер» словарь, содержащий каждый из их user_dict словарей (которые основаны на их User информации) и добавить к этому словарь для каждого из их навыков. Затем, вернувшись в шаблон, я хочу перебрать «супер» словарь таким образом, чтобы он представил их примерно так:

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

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

Вот шаблон, с которым я пытаюсь работать:

{% 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 %}

Когда я вижу страницу сейчас, она выглядит так:

Our Developers

Да... ничего не заселяется. Какие-либо предложения?

ОБНОВЛЕНИЕ 1: я изменил свою утилиту по предложению iMom0. Теперь я использую список для каждого навыка. Вот так:

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

Я вижу ценность в этом - на самом деле, это гораздо более интуитивно понятно, и я думаю, что в противном случае я слишком усложнял это. Но мой шаблон все еще отображается голым. :(

ОБНОВЛЕНИЕ 2:

Я знаю, что сейчас на пути к написанию. Я помещаю некоторые журналы в представление:

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]))

И в файле журнала отображается:

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

Итак, это должно быть так, как я называю это в шаблоне, верно?

ОБНОВЛЕНИЕ 3:

У меня было понимание, что я разъединяю user_dict и их skills. Поэтому я немного модифицировал утилиту, чтобы объединить их в один словарь.

## 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

Это гораздо лучшее решение, на мой взгляд. Однако у меня все еще возникают проблемы с доступом к информации user_dict в шаблоне. :(


person Rico    schedule 20.10.2012    source источник


Ответы (3)


Вы можете использовать функции Django ORM, чтобы сделать это намного проще (и, как мы увидим, повысить производительность), это отличная функция!

Код модели

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

Мы изменили две вещи:

  • Использование UserProfile ForeignKey вместо user упростит остальную часть кода. Поскольку у вас в любом случае есть отображение UserProfile ‹-> User, это не будет проблемой.
  • Мы добавили атрибут related_name, чтобы любой объект UserProfile теперь имел атрибут skill_set, в котором хранится его список DevSkills.

(Обратите внимание, что related_name не требуется, и Django создаст общий атрибут modelname_set, если вы его не установите). Кроме того, DevSkill должно быть в единственном числе, объект представляет собой один навык!

Я также ожидаю, что у вас есть следующее для UserProfile, и вы создали код, предполагая, что вы это сделали. Вам нужно будет адаптироваться, если вы этого не сделаете.

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 %}

Производительность

Имейте в виду, что этот код (тот, который вы тоже используете сейчас) абсолютно убьет производительность. Действительно, мы делаем несколько запросов для каждого пользователя (обращаясь к базе данных для их пользователя и их DevSkills).

Это не проблема, мы можем использовать ORM select_related и prefetch_related для решения этой проблемы:

devs = UserProfile.objects.select_related('user').prefetch_related('skill_set').all()
Таким образом, мы делаем только два запроса, один для UserProfile -> User и один для DevSkill, для которых соединение выполняется на Python, но вам не нужно об этом заботиться, Django сделает это за вас.

Имейте в виду, что prefetch_related — это функция Django 1.4.


Сноска: в Django 1.5 больше не будет UserProfile, посмотрите!

person Thomas Orozco    schedule 20.10.2012
comment
Извините, но это не работает для меня. Если я использую запрос devs = UserProfile.objects.all(is_staff = True, is_superuser = False), я получаю сообщение об ошибке, потому что объект User имеет атрибуты is_staff и is_superuser, а не модель UserProfile. Как я могу отфильтровать UserProfile по пользователям, которым is_staff, а не is_superuser? - person Rico; 20.10.2012
comment
Хорошо, я смог использовать последний запрос, который вы указали devs = UserProfile.objects.select_related('user').prefetch_related('skill_set').all(), но он возвращает всех пользователей. Я хочу только is_staff, а не is_superuser. Как я могу это сделать? - person Rico; 20.10.2012
comment
Я понял! Я просто обработал это в шаблоне с помощью простого {% if dev.user.is_staff and not dev.user.is_superuser %}. Большое спасибо! - person Rico; 20.10.2012
comment
@Rico Это сработает, но вы все еще запрашиваете все и только решаете, отображать это или нет. Если у вас большое количество пользователей, это может стать проблемой. Вы должны использовать UserProfile.objects.filter(user__is_staff = True, user__is_superuser = False).... Синтаксис rel__field позволяет отслеживать отношения в запросах. - person Thomas Orozco; 21.10.2012

dict.update всегда перезаписывать значение dict

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

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

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

Вместо этого вы должны использовать list и list.append.

А ваш шаблон не знает что такое user_dict, поправьте,

dev_dict['user_dict'] = user_dict
person iMom0    schedule 20.10.2012
comment
@Rico d['key'] = значение совпадает с d.update({'key': value}), а не d.update(value) - person iMom0; 20.10.2012

Это новая концепция для меня - dict.items в шаблоне. Ниже показано, как именно я смог отобразить то, что хотел.

{% 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 %}
person Rico    schedule 20.10.2012
comment
Вы можете использовать dict.values(), если вам не нужен key. Но если вам не нужен ключ, вам, вероятно, следует использовать вместо него list. - person Thomas Orozco; 20.10.2012