简体   繁体   中英

Django Template Attributes of QuerySet for ManyToMany with “Through” Class

I've got roughly models like these:

class Person(models.Model):
  ...
  name = models.CharField(max_length=40)
  eyes = models.CharField(max_length=12)
  ...
  relationships = models.ManyToManyField('self', through='Relationship',
    symmetrical=False, related_name='related_to')

class Relationship(models.Model):
  from_person = models.ForeignKey(Person, related_name='from_people', ...)
  to_person = models.ForeignKey(Person, related_name='to_people', ...)
  status = models.CharField(max_length=20)

I've got reasonable database entries, and can see them in the Python shell:

>>> p1 = Person.objects.get(...)
>>> r = Relationship.objects.filter(from_person=p1)
<QuerySet [<Relationship: p1 p2 status>], [<Relationship: p1 p99 status>]>
>>> r[0].status
Friend
>>> r[0].from_person.name
p1
>>> r[0].from_person.eyes
Brown    # p1's eye color
>>> r[0].to_person.name
p2
>>> r[0].to_person.eyes
Blue    # p2's eye color !!!

Now I want to access this same information from my templates, but I don't see what I expect:

<p>name={{ person.name }} ({{ person.eyes }})</p>
<p>len={{ person.relationships.all|length }}</p>
{% for rel in person.relationships.all %}
  <p> rel[{{ forloop.counter }}]={{ rel.status }}
        {{ rel.from_person.name }} ({{ rel.from_person.eyes }})
        {{ rel.to_person.name }}   ({{ rel.to_person.eyes }})</p>
{% endfor %}

This displays:

name=p1 (Brown)
len=2
rel[0]=
rel[1]=

I expect:

name=p1 (Brown)
len=2
rel[0]=Friend p1 (Brown) p2 (Blue)
rel[1]=Enemy p1 (Brown) p99 (Red!)

I added accessor methods to the 'Relationship' model, but it didn't make a difference. I messed with using the template "slice" filter. I'm missing something basic...

Thanks for your help!

edit: This template code does what I want for the example above. Thanks to the two commenters below:

{% for rel in person.from_people.all %}
  <p> rel[{{ forloop.counter }}]={{ rel.status }}
        {{ rel.from_person.name }} ({{ rel.from_person.eyes }})
        {{ rel.to_person.name   }} ({{ rel.to_person.eyes   }})</p>
{% endfor %}

Look at this line of code in your Person model:

relationships = models.ManyToManyField('self', through='Relationship', ...)

If you address the attribute relationships of a Person instance, it returns a Manager for the target model (which is also Person ).

So p1.relationships.all() returns all persons that p1 is related to, not the Relationship instances.

If you want to access the intermediate data of your through model, you have to directly query it:

Relationship.objects.filter(from_person=p1)

See also the examples in the Docs (Django 2.0): Extra fields on many-to-many relationships

Edit : Dan's comment might show the correct way to access the Relationship data given a Person from the template. I think the template language should be able to handle backward relations. Can you try this and tell if it works?

<p>name={{ person.name }} ({{ person.eyes }})</p>
<p>len={{ person.relationships.all|length }}</p>
{% for rel in person.from_people.all %}
  {# rel is now a Relationship where 'from_person' is 'person' #}
  <p> rel[{{ forloop.counter }}]={{ rel.status }}
        {{ rel.from_person.name }} ({{ rel.from_person.eyes }})
        {{ rel.to_person.name }}   ({{ rel.to_person.eyes }})</p>
{% endfor %}

This template code does what I want for the example above. Thanks to the two commenters:

{% for rel in person.from_people.all %}
  <p> rel[{{ forloop.counter }}]={{ rel.status }}
        {{ rel.from_person.name }} ({{ rel.from_person.eyes }})
        {{ rel.to_person.name   }} ({{ rel.to_person.eyes   }})</p>
{% endfor %}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM