pythondjangolistsortingmanytomanyfield

How to sort a list(Paginator) containing a manytomany field


Python: 3.9.10 Django: 4.0.3

I am using Django Paginator.

To sort the data on each page, and only the data on that page, I am passing my queryset to Paginator, converting Paginator to a list and then sorting the list by its key using attrgetter().

My model contains two ManyToManyFields and so I cannot sort it using the standard sorted() method as I receive the error message:

TypeError: '<' not supported between instances of 'ManyRelatedManager' and 'ManyRelatedManager'

I think I understand why this is happening - sorted() cannot compare two lists (manytomanyfields) against each other and determine which is greater than or less than - so therefore the list(Paginator) cannot be sorted in this way.

I imagine I probably have to iterate through the list(Paginator), get the first value of the manytomanyfield and then somehow sort everything by that. I am not what the best way is to go about doing this.

models.py

class DataExchange(models.Model):
    name = models.CharField(unique=True, max_length=15, null=True)

    def __str__(self):
        return self.name

class DataSource(models.Model):
    name = models.CharField(unique=True, max_length=10, null=True)

    def __str__(self):
        return self.name

class Provider(models.Model):
    name = models.CharField(unique=True, max_length=15, null=True)
    exchange = models.ManyToManyField(DataExchange, related_name="providers_exchange")
    source = models.ManyToManyField(DataSource, related_name='providers_source')
    score = models.IntegerField(null=True)

    def __str__(self):
        return self.name

Paginator helper function: order_by = "exchange"

def paginator_helper(request, paginator):

    page = request.GET.get("page")
    
    if page is None:
        page = 1
    try:
        page_obj = paginator.get_page(page)
    except PageNotAnInteger:
        page_obj = paginator.page(1)
        page = 1
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)
        page = page_obj.number
    
    order_by = request.GET.get("orderby")
    
    if order_by is None:
        order_by = "name"
    
    ascending = request.GET.get("ascending")
    if ascending is None:
        ascending = "0"
    
    if ascending == "1":
        ascending = True
    else:
        ascending = False
     
    page_obj = list(page_obj)
            
    if ascending:
        page_obj = sorted(page_obj, key=attrgetter(order_by))
        
    else:
        page_obj = sorted(page_obj, key=attrgetter(order_by), reverse=True)
    
    return page_obj, paginator_obj, order_by, ascending

Solution

  • Using annotation did solve the problem initially, though it created additional rows which caused pagination num_pages to be an invalid number. The best way to solve this problem is not to use manytomanyfield but rather to use https://pypi.org/project/django-multiselectfield/ - this way the data is saved as a charfield, comma-separated - which can be sorted like any other field.