Django ManyToManyField ordering using through

Viewed 25030

Here is a snippet of how my models are setup:

class Profile(models.Model):     
    name = models.CharField(max_length=32)

    accout = models.ManyToManyField(
        'project.Account',
        through='project.ProfileAccount'
    )

    def __unicode__(self)
        return self.name

class Accounts(models.Model):
    name = models.CharField(max_length=32)
    type = models.CharField(max_length=32)

    class Meta:
        ordering = ('name',)

    def __unicode__(self)
        return self.name

class ProfileAccounts(models.Model):
    profile = models.ForeignKey('project.Profile')
    account = models.ForeignKey('project.Accounts')

    number = models.PositiveIntegerField()

    class Meta:
        ordering = ('number',)

Then when I access the Accounts, how can I sort by 'number' in the intermediary ProfileAccounts model, rather than the default 'name' in the Accounts Model?:

for acct_number in self.profile.accounts.all():
    pass

This does not work, but is the gist of how I want it to access this data:

for scanline_field in self.scanline_profile.fields.all().order_by('number'):
    pass
7 Answers

a ManyToManyField manager allows you to select/filter data from the related model directly, without using any model connection of the through model on your django level...

Likewise,

if you try:

pr = Profile.objects.get(pk=1)
pr.account.all()

returns you all account related to that profile. As you see, there exists no direct relation to the through model ProfileAccount, so you can not use the M2M relation at this point... You must use a reverse relation to the through model and filter the results...

pr = Profile.objects.get(pk=1)
pr.profileaccount_set.order_by('number')

will give you an ordered queryset, but, in this case, what you have in queryset is profileaccount objects, not account objects... So you have to use another django level relation to go to each related account with:

pr = Profile.objects.get(pk=1)
for pacc in pr.profileaccount_set.order_by('number'):
    pacc.account

I have this on a number of my models, but in my opinion (and unlike all the other answers) you shouldn't need to specify the order_by again because, well, it's already specified in the through model. Specifying it again breaks the DRY (don't repeat yourself) principle.

I would use:

qs = profile.profileaccounts_set.all()

This gives the set of ProfileAccounts associated with a profile using your configured ordering. Then:

for pa in qs:
    print(pa.account.name)

For bonus points, you can also speed up the overall process by using select_related in the query.

Related