Need Help Fixing Many-to-Many Query and Template Loop

Hi all,

I’m hitting a wall as a newbie trying to write a query that finds a single object (in this case an art collection) and its related objects (in this case the collectors who own the art collection) in a many-to-many relationship. I then need to loop through the related objects (owners) in a template. I think I’m not too far off, but does anyone see the obvious problem? Your help MUCH appreciated. All looks good in admin per attached image.

MODELS

collections_app/models.py

class Collection (models.Model):
    collector = models.ManyToManyField(to='collectors_app.Collector',
                                           related_name='collections',
                                           verbose_name=u'Collector(s)',
                                           blank=True ...

name = models.CharField('Collection Name',
                        max_length=255,
                        blank=False,                            
 [snip]

collector_app/models.py

class Collector (models.Model):
    inst_main_name = models.CharField('Institution/Dealer/Consortium Main Name',
                                      max_length=255,
                                      help_text='If an institutional collection, provide main institution '
                                                'name. If consortium or collaboration, suppy official name '
                                                'or generalized name (use notes field to provide details). '
                                                'If a dealer, provide business name.',
                                      blank=True)
 [snip]

VIEW - receives a collection id in a URL such as “http://127.0.0.1:8000/collections/3/

def collection_detail(request, collection_id):
     collection = Collection.objects.filter(pk=collection_id).prefetch_related('collector').all()
     template = 'collections_app/collection_detail.html'
     context = {'collection': collection}
     return render(request, template, context)

TEMPLATE

<ul class="body">
    {% for collection in collection %}
        <li><a href="">{{ collection.inst_main_name }}</a></li>
    {% endfor %}
</ul>

So the set of all Collector objects related to that Collection is collector.all()

That means in your template you can iterate over collection.collector.all.

BTW, I think you’re be better off trying to avoid confusion in the future by not using “collection” in multiple places for multiple things. This is likely to lead to very-hard-to-diagnose problems in the future.

Hi Ken, thanks for looking at this. I know, it’s confusing for me when you’re dealing with collectors and collections.

Still stuck here. I can’t even get a value for “collection.name” from the following. I would expect to at least be able to access the name field of the collection. What I expect the query to do is to get a unique collection based on the collection id, then get related collector objects with the prefetch. I can’t seem to get anything out of it, not even attributes of the collection, much less collector. What’s amiss?

collection = Collection.objects.filter(pk=collection_id).prefetch_related('collector_mul').all()

That’s correct.

collection = Collection.objects.filter(pk=collection_id).prefetch_related('collector_mul').all()
will return a queryset, not an individual collection - even when the query is only going to retrieve one item.

If you want collection to only have the single Collection object with that specific collection_id, try:

collection = Collection.objects.prefetch_related('collector_mul').get(pk=collection_id)

Ken

Ken, thanks, let me give that a go. Does my template look correct?

The get query errors:

Exception Type: TypeError
Exception Value: ‘Collection’ object is not iterable

This is where having multiple things all called “collection” is causing confusion.

Given a single collection object being passed to the template, where you want to iterate through all the related Collector objects through the name Collector, you’re going to want something like this:

{% for a_collector in collection.collector %}
  {{ a_collector.inst_main_name }}
{% endfor %}

Ken, I don’t know what’s happening. Nothing is passed here:

Collection name: {{ collection.name }}

{% for collector in collection.collector %}
{{ collector.inst_main_name }}
{% endfor %}

Let’s take a step back - I know a number of different things have been discussed here, so please repost what your current view looks like. (I’m going to assume that your model hasn’t changed since the original post. Please repost the models if they are different.)

Sure thing, Ken. It might be worth noting that the “collector_mul” field in the Collection model is the many-to-many. The model also has a “collector” field that I using before as a foreign key. I had to change because I was made a aware of a case where a collection could actually be “owned” by multiple collectors.

def collection_detail(request, collection_id):
    collection = Collection.objects.filter(pk=collection_id).prefetch_related('collector_mul').all()
    template = 'collections_app/collection_detail.html'
    context = {'collection': collection}
    return render(request, template, context)
  • From the earlier post, your query should be changed to:
    collection = Collection.objects.prefetch_related('collector_mul').get(pk=collection_id)

  • then, in your template:

{% for a_collector in collection.collector_mul %}
  {{ a_collector.inst_main_name }}
{% endfor %}

As I wrote your earlier, that query errors:

Exception Type: TypeError
Exception Value: ‘Collector’ object is not iterable

What do you mean that that query throws that error? Please post the full context of where you’re seeing that error.

Also please post the complete template.

When I load “http://127.0.0.1:8000/collections/5/”, passing the collection id, I get attached.

Cool, now we’re making progress - if you read the error message, the error is being thrown by the “render” function, indicating a problem in the template, not in the query.

There’s nothing wrong with the query, the problem is how the template is attempting to render the results of the query.

Not sure how we iterate collector then. We know there should be three collectors associated with collection_id 5.

collection.name works btw.

Man, you’re a champ to help me through this issue. Thanks mucho.

From the earlier post, you iterate through all the collectors associated with a collection through the collector_mul many-to-many field:

{% for a_collector in collection.collector_mul %}
  {{ a_collector.inst_main_name }}
{% endfor %}
1 Like

Another error per attached.

Sorry, silly me - that first line should be:

{% for a_collector in collection.collector_mul.all %}

(I make that mistake more often than I will ever admit.)