Virtu_Acad
Virtu_Acad

Reputation: 141

The best way to do an efficient filter query in django

models.py file

I am not so good at this aspect in Django. Please can someone help me? I wish to know if there is a more efficient way for the class method already_voted

class Vote(TimeStamped):
    voter = models.ForeignKey(get_user_model(), verbose_name=_("Vote"), on_delete=models.CASCADE)
    contester = models.ForeignKey(Contester, verbose_name=_("Contester"), on_delete=models.CASCADE, 
    help_text=("The chosen contester"), related_name="votes")
    ip_address = models.GenericIPAddressField(
    _("Voter's IP"), 
    protocol="both", 
    unpack_ipv4=False, 
    default="None",
    unique=True
    )
    num_vote = models.PositiveIntegerField(_("Vote"), default=0)

    class Meta:
        unique_together = ('voter','contester')
        verbose_name = _("Vote")
        verbose_name_plural = _("Votes")
    
        permissions = (
                   ("vote_multiple_times", "can vote multiple times"),
                  )
   ....
   ....
    @classmethod
    def already_voted(cls, contester_id, voter_id=None, ip_addr=None):
        return cls.objects.filter(contester_id=contester_id).exists() and \
           (cls.objects.filter(ip_address=ip_addr).exists() or \
           cls.objects.filter(voter_id=voter_id).exists())

Upvotes: 0

Views: 50

Answers (1)

dani herrera
dani herrera

Reputation: 51715

The class method may be right, but your model needs one more index:

    contester = models.ForeignKey( db_index= True #... )

Notice that:

  • voter doesn't need index because is on first place on unique_together constraint.
  • contester needs index because, despite it is on unique_together, doesn't is place on first position of the constraint.
  • ip_address doesn't need index because has unique constraint.

Also:

  • unique_together is deprecated and should be a list of tuples (not just a tuple)

Edited

Edited 5 feb 2021 due to OP comment

You can get results in just one hit using Exists but it is less readable, also, I'm not sure if it is more efficient or the best way:

from django.db.models import Exists
q_ip=Vote.objects.filter(ip_address="1")
q_voter=Vote.objects.filter(voter=2)
already_voted=(
    Vote
    .objects
    .filter(contester=3)
    .filter(Exists(q_ip)|Exists(q_voter))
    .exists())

The underlying sql, you can see this is just one query:

SELECT ( 1 ) AS "a"
FROM   "a1_vote"
WHERE  ( "a1_vote"."contester" = 3
         AND ( EXISTS(SELECT U0."id",
                             U0."voter",
                             U0."contester",
                             U0."ip_address",
                             U0."num_vote"
                      FROM   "a1_vote" U0
                      WHERE  U0."ip_address" = '1')
                OR EXISTS(SELECT U0."id",
                                 U0."voter",
                                 U0."contester",
                                 U0."ip_address",
                                 U0."num_vote"
                          FROM   "a1_vote" U0
                          WHERE  U0."voter" = 2) ) )
LIMIT  1 

Upvotes: 1

Related Questions