开发者

A less expensive way to find the position of a model in a queryset?

开发者 https://www.devze.com 2023-03-09 20:27 出处:网络
I have two models: class Person(models.Model): name = models.CharField() email = models.EmailField() class Vote(models.Model):

I have two models:

class Person(models.Model):
    name = models.CharField()
    email = models.EmailField()

class Vote(models.Model):
    person = models.ForeignKey('Person', related_name='vo开发者_运维技巧tes')
    email = models.EmailField()

I can get the position of a person using this method on the person model:

@property
def position(self):
    person_list = Person.objects.annotate(Count('votes')).order_by(
        '-votes__count', 'name')
    for i, x in enumerate(person_list, start=1):
        if x == self:
            return i

The problem is the person_list queryset is evaluated every time the position method is called which in my opinion is firing off unnecessary queries to the database as this query only needs to be run once during the request/response cycle. Ideally I want to take advantage of the queryset cache. Does anyone have any idea how I would do that?

Thanks.

EDIT: I'm calling the position method from a template so I don't think I can pass in the queryset as an arg.


You could just store it within the model

@property
def position(self):
    if not self.entry_list: 
        self.entry_list = Entry.objects.annotate(Count('votes')).order_by(
            '-votes__count', 'name')
    for i, x in enumerate(self.entry_list, start=1):
        if x == self:
            return i

However, this query seems to be not model specific, I would more likely use a Manager to make the query and store it locally there.

class PersonManager(models.Manager):
    def most_votes(self):
        if not self.most_votes_queryset:
            self.most_votes_queryset = self.get_query_set()\
                .annotate(Count('votes'))\
                .order_by('-votes__count','name')
        return self.most_votes_queryset

class Person(models.Model):
    objects = VoteManager()

which would then result in your model:

@property
def position(self):
    entry_list = Person.objects.most_votes()
    for i, x in enumerate(entry_list, start=1):
        if x == self:
            return i


What about something like that:

def position(self):
    if hasattr(self, '_position'):
        return self._position
    person_list = Person.objects.annotate(vc=Count('votes'))\
        .order_by('-vc', 'name')
    num_votes = self.votes.count()
    place = person_list.filter(vc__gt=num_votes).count()

    place += person_list.filter(vc=num_votes, name__lte=self.name).count()

    self._position = place
    return place

I think this should work *better* if you have many records in the table of the Person model and it's caching position in model object.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号