Trouble Using dictsort in Template

Hi all,

I’m having trouble getting dictsort to work in a template. The first approach seems to me to be the most reasonable, and the second I tried just for grins. Ideally I would like to apply two levels or sorting, first on collector.inst_main_name, then on collector.person_last_name.

{% for collection in collections %}
   {% for collector in collection.collector.all|dictsort:'collector.inst_main_name' %}
        <li>
        <a href="/collections/{{ collection.id }}">
        {{ collector.inst_main_name }}{% if collector.inst_sub_name %}, {% endif %}
        {{ collector.inst_sub_name }}
        {{ collector.person_last_name }}{% if collector.person_last_name %}, {% endif %}
        {{ collector.person_first_name }} &mdash; {{ collection.name }}</a>
                    {% if collection.consortium == True %}<strong class="consortium">&#x26AD;</strong>
{% endif %}</li>

{% for collection in collections|dictsort:'collection.collector.inst_main_name' %}
   {% for collector in collection.collector.all %}
      <li>
        <a href="/collections/{{ collection.id }}">
        {{ collector.inst_main_name }}{% if collector.inst_sub_name %}, {% endif %}
        {{ collector.inst_sub_name }}
        {{ collector.person_last_name }}{% if collector.person_last_name %}, {% endif %}
        {{ collector.person_first_name }} &mdash; {{ collection.name }}</a>
        {% if collection.consortium == True %}<strong class="consortium">&#x26AD;</strong>
{% endif %}</li>

dictsort works on a list of dictionaries or any object implementing __getitem__ using a specific numeric index.

collection.collector.all is a queryset. I don’t see where the allowed syntax within the template language is going to allow for this usage by default. I think you may need to set the default ordering in the model’s Meta class to have it return the data in the desired order.

Hi Ken,

Always reliable. Yes, I tried that. My Collector Meta class is thus:

class Meta:
verbose_name = ‘Collector’
verbose_name_plural = ‘** Collectors’
ordering = [‘inst_main_name’, ‘person_last_name’]

My view is:

def browse_all_collections(request):
    collections = Collection.objects.prefetch_related('collector').all()
    template = 'collections_app/browse_collections.html'
    context = {'collections': collections}
    return render(request, template, context)

So to me it looks like collection objects come from the query, so the template loop sorts by collection. I don’t know how to change that. Is there something we can do in the view to sort by collector?

Under the assumption that there’s a many-to-many relationship between the Collection and Collector objects, that wouldn’t make a lot of sense.
Assume the following (very simplified) relationships:

Collection Collector
Smithsonian Smith
Smithsonian Jones
MUMA Jones
MUMA Franklin
Walters Franklin
Walters Smith

In what order would you want these returned?

It does make sense to sort by Collector within Collection, which can be done using the order_by clause on the query.

Or, if the intent of this view is to primarily show the Collectors in their order, you can do your query on the Collector objects and then retrieve the related Collection data via the many-to-many relationship.

It’s sort of working with:

collections = Collection.objects.prefetch_related('collector').order_by('collector').all()

BUT, collections with multiple collectors get repeated (in this case, each gets three entries, the same number of collectors associated with the collection), and I’m not quite sure why Mullen Books is out of order. Also, Barker is out of order since he is a personal collector. I actually need the sort to first sort on inst_main_name, then down to person_last_name.

Also tried this, but it doesn’t change anything.

collections = Collection.objects.prefetch_related('collector').order_by('collector__inst_main_name',
                                                                            'collector__person_last_name'
                                                                            ).all()

From the Django shell, do:
print(Collection.objects.prefetch_related(‘collector’).order_by(‘collector’).all().query)

That will show you the query being issued to the database. (I think I know what’s going on, but I’m not sure and so would need to see the query being generated.)

Ken

SELECT "collections_app_collection"."id", "collections_app_collection"."consortium", "collections_app_collection"."name", "collections_app_collection"."description", "collections_app_collection"."quote", "collections_app_collection"."quote_attrib", "collections_app_collection"."access", "collections_app_collection"."website", "collections_app_collection"."size", "collections_app_collection"."dig_projects", "collections_app_collection"."dig_access", "collections_app_collection"."loc_city", "collections_app_collection"."loc_state_prov", "collections_app_collection"."loc_country", "collections_app_collection"."notes", "collections_app_collection"."date_created", "collections_app_collection"."date_saved" FROM "collections_app_collection" LEFT OUTER JOIN "collections_app_collection_collector" ON ("collections_app_collection"."id" = "collections_app_collection_collector"."collection_id") LEFT OUTER JOIN "collectors_app_collector" ON ("collections_app_collection_collector"."collector_id" = "collectors_app_collector"."id") ORDER BY "collectors_app_collector"."inst_main_name" ASC, "collectors_app_collector"."person_last_name" ASC

You’re getting the collections repeated because of the many-to-many relationship you have created.

Assuming you have Collections A and B, each relating to Collectors X and Y, the query you’ve built will return the following result set:

Collection Collector
A X
A Y
B X
B Y

(This is being caused by the order_by clause in the query creating the join on the collector table. Again, it doesn’t really make sense to try and sort a base table based on the order of a subordinate table referenced through a many-to-many query.)

So you want your collections = Collection.objects.prefetch_related(‘collector’). If you want the collections sorted, you can add the order_by clause to this query.
If your collector objects then have the right ordering in them, you can retrieve them in order through a_collection.collectors.all in your template.

Hi Ken,

Still working on this problem.

This view …

def browse_all_collections(request):
    collections = Collection.objects.prefetch_related('collector').all()
    template = 'collections_app/browse_collections.html'
    context = {'collections': collections}
    return render(request, template, context)

produces a sort by collection name, which is exactly what I would expect since we’re retrieving Collection objects.

When I add “order_by” to the view …

def browse_all_collections(request):
    collections = Collection.objects.prefetch_related('collector').order_by(
        'collector__inst_main_name', 'collector__person_last_name', 'name').all()
    template = 'collections_app/browse_collections.html'
    context = {'collections': collections}
    return render(request, template, context)

I get …

I think you tried to explain what is happening, but I still don’t get it. I can’t discern a collector order, and is there any way to get rid of the duplicates? Forgive for revisiting this. I’m not sure what the solution might be. Can you think of another approach?

The sort I am trying to achieve seems pretty simple. Sort by collector fields, “inst_main_name” or “person_last_name”, which ever is present, then by “name” of collection.

There’s still a degree of ambiguity in what you’re trying to describe.

I’m also still not clear how the data you’re showing here aligns with the data fields you’re using in your models.

It would be really helpful if you could post your models in this thread, along with a minimal set of data displaying the issue you’re trying to resolve.

Also, can you put together a list of exactly the order that you want so see this list displayed? (Paying particular attention to how you want things grouped and what you want in each group.)

Hi Ken,

I’m back on this problem after focusing on other areas of the site.

All of my models are here and up to date, both in the collectors_app and the collections_app.

View I’m using is “browse_all_collections” here:

I thought the screen shot I supplied in my last post pretty clearly showing the sorting anomalies I’m seeing. Since that post, I have added a “sort_name” field to my collectors model to facilitate sorting names minus initial articles.

As you can see from the screenshot, sorting is all over the place. Sort should be by:

  • ‘collector__sort_name’
  • ‘collector__inst_sub_name’
  • ‘collector__inst_sub2_name’
  • ‘name’ [name of collection]

Thanks again, for taking the time to help me again.

Sam

Sam,

Ok, you’ve now clarified that the listing you’re trying to provide is “by collector”.

That means that you don’t want to generate your list as a list of collections. You want your query to be based on the Collector, referencing the name of the Collection in the template.

In other words, what you’re really trying to do is something like:

all_collectors = Collector.objects.order_by(‘sort_name’, ‘inst_sub_name’, ‘inst_sub2_name’).prefetch_related(‘collection’), where Collection has a default ordering of ‘name’.

That is the list you want to iterate over in your template.

Ken

Ken, I totally get your logic here, and I remember trying this approach before you suggested it. Thing is, it errors because there is no field “collection” on the collector model; the many-to-many field, “collector”, lives in the collection model.

# AttributeError at /collections/browse/

Cannot find 'collection' on Collector object, 'collection' is an invalid parameter to prefetch_related()

|Request Method:|GET|
| --- | --- |
|Request URL:|http://127.0.0.1:8000/collections/browse/|
|Django Version:|3.0.5|
|Exception Type:|AttributeError|
|Exception Value:|Cannot find 'collection' on Collector object, 'collection' is an invalid parameter to prefetch_related()|
|Exception Location:|/Users/samduncan/Dev/artist-files-directory/lib/python3.8/site-packages/django/db/models/query.py in prefetch_related_objects, line 1638|
|Python Executable:|/Users/samduncan/Dev/artist-files-directory/bin/python3|
|Python Version:|3.8.2|
|Python Path:|['/Users/samduncan/Dev/artist-files-directory', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python38.zip', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/lib-dynload', '/Users/samduncan/Dev/artist-files-directory/lib/python3.8/site-packages']|
|Server time:|Thu, 25 Jun 2020 15:55:09 +0000|

From your model:

    collector = models.ManyToManyField(to='collectors_app.Collector',
                                       related_name='collections',
                                       verbose_name=u'Collector(s)',
                                       blank=False,
                                       help_text='Create or choose a collector responsible for the '
                                                 'artist files collection. A collection can have '
                                                 'multiple owners, for example, in the case of '
                                                 'consortial or collaborative digital projects.')

From the documentation on Many to Many Relationships:

Like ForeignKey , ManyToManyField can specify related_name . In the above example, if the ManyToManyField in Entry had specified related_name='entries' , then each Author instance would have an entries attribute instead of entry_set .

That’s why you’ve got a related_name attribute in your model above and shows how it’s referenced.

Eureka, Ken, thanks for your patience with me! Every victory leads to deeper understanding. Kow-tow!!