David542
David542

Reputation: 110093

Ordering by a custom model field in django

I am trying to add an additional custom field to a django model. I have been having quite a hard time figuring out how to do the following, and I will be awarding a 150pt bounty for the first fully correct answer when it becomes available (after it is available -- see as a reference Improving Python/django view code).

I have the following model, with a custom def that returns a video count for each user --

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    positions = models.ManyToManyField('Position', through ='PositionTimestamp', blank=True)

    def count(self):
        from django.db import connection
        cursor = connection.cursor()
        cursor.execute(
        """SELECT (
            SELECT COUNT(*)
                FROM videos_video v
                WHERE v.uploaded_by_id = p.id
                OR EXISTS (
                    SELECT NULL
                    FROM videos_videocredit c
                    WHERE c.video_id = v.id
                    AND c.profile_id = p.id
                )
            ) AS Total_credits
            FROM userprofile_userprofile p
            WHERE p.id = %d"""%(int(self.pk))
        )
        return int(cursor.fetchone()[0])

I want to be able to order by the count, i.e., UserProfile.objects.order_by('count'). Of course, I can't do that, which is why I'm asking this question.

Previously, I tried adding a custom model Manager, but the problem with that was I also need to be able to filter by various criteria of the UserProfile model: Specifically, I need to be able to do: UserProfile.objects.filter(positions=x).order_by('count'). In addition, I need to stay in the ORM (cannot have a raw sql output) and I do not want to put the filtering logic into the SQL, because there are various filters, and would require several statements.

How exactly would I do this? Thank you.

Upvotes: 0

Views: 1350

Answers (3)

StefanNch
StefanNch

Reputation: 2609

models:

class Positions(models.Model):
    x = models.IntegerField()

    class Meta:
        db_table = 'xtest_positions'

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    positions = models.ManyToManyField(Positions)

    class Meta:
        db_table = 'xtest_users'

class Video(models.Model):
    usr = models.ForeignKey(UserProfile)
    views = models.IntegerField()

    class Meta:
        db_table = 'xtest_video'

result:

test = UserProfile.objects.annotate(video_views=Sum('video__views')).order_by('video_views')
for t in test:
    print t.video_views

doc: https://docs.djangoproject.com/en/dev/topics/db/aggregation/

This is either what you want, or I've completely misunderstood!.. Anywhoo... Hope it helps!

Upvotes: 0

John Mee
John Mee

Reputation: 52233

My reaction is that you're trying to take a bigger bite than you can chew. Break it into bite size pieces by giving yourself more primitives to work with.

You want to create these two pieces separately so you can call on them:

  • Does this user get credit for this video? return boolean
  • For how many videos does this user get credit? return int

Then use a combination of @property, model managers, querysets, and methods that make it easiest to express what you need.

For example you might attach the "credit" to the video model taking a user parameter, or the user model taking a video parameter, or a "credit" manager on users which adds a count of videos for which they have credit.

It's not trivial, but shouldn't be too tricky if you work for it.

Upvotes: 1

niklasdstrom
niklasdstrom

Reputation: 742

"couldn't you use something like the "extra" queryset modifier?"

see the docs

I didn't put this in an answer at first because I wasn't sure it would actually work or if it was what you needed - it was more like a nudge in the (hopefully) right direction.

in the docs on that page there is an example

query

Blog.objects.extra(
    select={
        'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id'
    },
)

resulting sql

SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count
FROM blog_blog;

Perhaps doing something like that and accessing the user id which you currently have as p.id as appname_userprofile.id

note:

Im just winging it so try to play around a bit. perhaps use the shell to output the query as sql and see what you are getting.

Upvotes: 0

Related Questions