2

When a ManyToMany relationship has extra data via a through table, how can you get to the data in a template? From a view I can get the data if I supply parameters:

class Category(models.Model):
    title           = models.CharField(max_length=1024,null=True,blank=True)
    entry           = models.ManyToManyField(Entry,null=True,blank=True,
                                             related_name='category_entry',
                                             through='CategoryEntry',
                                             )

class CategoryEntry(models.Model):
    category    = models.ForeignKey(Category)
    entry       = models.ForeignKey(Entry)
    votes       = models.IntegerField(null=False, default=0)

def category_detail(request, pk):
    category = models.Category.objects.select_related().get(pk=pk)
    entries  = category.entry.order_by('-temp_sort_order').filter(temp_sort_order__gte=0)
    for entry in entries:
        assert isinstance(entry, models.Entry)
        ce = models.CategoryEntry.objects.get(entry=entry, category=category)
        pprint('Show votes as a test: ' + ce.votes) #OK
        pprint('entry title: ' + entry.title) #OK
        pprint('entry votes: ' + str(entry.category_entry.votes)) #BAD
        pprint('entry votes: ' + str(entry.entry.votes))  #BAD
    ....

But templates can't supply parameters to methods.

The documentation at https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships is silent on templates. Using using for entry in category.category_entry_set.all gives 'Category' object has no attribute 'category_entry_set'. category.category_entry.all does not work either.

Ultimately I want to display the extra data in a template:

{% for entry in entries %}
    <ul>
        <li>Title: {{ entry.title }} Votes: {{ entry.category_entry.votes }} {{ entry.entry.votes }}</li>
    </ul>
{% endfor %}
Bryce
  • 8,313
  • 6
  • 55
  • 73

3 Answers3

2

If you have a category instance in template:

category.entry.all -> list of entries

If you have an entry instance in template:

entry.category_entry.all -> list of categories

You should call M2M fields in plural form, then you will have a more readable code

category.entries.all

%model%_set syntax (or related name, if you've specified it) is using to access to model trough a backward relationship.

https://docs.djangoproject.com/en/1.4/topics/db/queries/#following-relationships-backward

But how do I get the 'votes' associated with the m2m instance? – Bryce

I suggest you the following way:

class Category(models.Model):
    title           = models.CharField(max_length=1024,null=True,blank=True)
    entries           = models.ManyToManyField(Entry,null=True,blank=True,
                                             related_name='categories',
                                             through='CategoryEntry',
                                             )

class CategoryEntry(models.Model):
    category    = models.ForeignKey(Category, related_name='category_entries')
    entry       = models.ForeignKey(Entry)
    votes       = models.IntegerField(null=False, default=0)

def category_detail(request, pk):
    category = models.Category.objects.select_related().get(pk=pk)
    category_entries  = category.category_entries.filter(entry__temp_sort_order__gte=0).order_by('-entry__temp_sort_order')
    for category_entry in category_entries:
        # category_entry is an instance of the model CategoryEntry
        pprint('category entry votes: ' + str(category_entry.votes))
        pprint('entry title: ' + category_entry.entry.title)
   ....

HOW TO
entry = Entry.objects.get(pk=1)
entry.categories.all() # list of categories (here we work through related name of the field entries)

category = Category.objects.get(pk=1)
category.entries.all() # list of entries (here we work through m2m field entries)

category.category_entries.all() # list of CategoryEntry objects (through related name category_entries of the field category in model CategoryEntry)
Andrei Kaigorodov
  • 2,145
  • 17
  • 17
1

Updating my answer, i mistakenly put related manager on wrong model, in your case, like Andrey said, the correct way to get entries from category is:

category.entry.all()

Now, to address your iteration and ordering question. In python it will look like this:

for ce in category.categoryentry_set.order_by('-votes'):
    print ce.entry, ce.votes

This will give you entries in each category ordered by votes. To get this to template you can just save a queryset category.categoryentry_set.order_by('-votes') into variable and iterate over it.

Dmitry Shevchenko
  • 31,814
  • 10
  • 56
  • 62
  • Adding 'for entry in category.category_entry.all: pprint('2: ' + entry)' results in error 'Category object has no attribute category_entry'. And remember the goal is to get something into the template, so it can display the unique extra data stored with each m2m record. Can you updated your answer with an idea of why it is not working here? – Bryce Jul 16 '12 at 06:30
  • Fixed my answer, sorry about that – Dmitry Shevchenko Jul 16 '12 at 16:10
  • Can you amend the answer to deal with templates? The view is filtering and sorting the entry. I want to display the 'votes' in the template. – Bryce Jul 16 '12 at 18:02
  • But how do I get the 'votes' associated with the m2m instance? – Bryce Jul 16 '12 at 18:15
0

Here's an ugly ugly hack that works. After the filter and sort, process the list and append the extra model fields. The templates now have easy access:

entries  = category.entry.order_by('-temp_sort_order').filter(temp_sort_order__gte=0)
for entry in entries:
    assert isinstance(entry, models.Entry)
    ce = models.CategoryEntry.objects.get(entry=entry, category=category)
    entry.xxx_votes = mark_safe(ce.votes)  # use {{ entry.xxx_votes to access }}
    entry.xxx_ce    = ce  # Use {{ entry.ce.votes to access }}
return render_to_response('category.html')

Hopefully someone can provide a better answer, or suggest an improvement to django itself. This solution does not allow me to sort: category.entry.order_by('-category_entry.votes')

Bryce
  • 8,313
  • 6
  • 55
  • 73