1

I have a Model defined as below:

class ProblemVerificationModel(CreateUpdateDateModel):
    problem = models.ForeignKey(Problem, related_name="problem_survey")
    verifier = models.ForeignKey(settings.AUTH_USER_MODEL,    related_name="verified_problem")

    is_problem_properly_formated = ... # BooleanField
    is_this_done = ... # BooleanField
    is_that_done = ... # BooleanField
    # other BooleanFields

The above model serves like a voting system in my application. I need to calculate percentage of votes for each property such as is_problem_properly_formated, is_this_done, is_that_done etc. I am using classmethod for the same as defined below:

@classmethod
def percentage_problem_properly_formated(cls, problem):
    votes = cls.objects.filter(problem=problem)
    total_votes = votes.count()
    yes_votes = votes.filter(is_problem_properly_formated=True).count()
    percentage_yes = (yes_votes / total_votes) * 100
    return percentage_yes

@classmethod
def percentage_is_this_done(cls, problem):
    votes = cls.objects.filter(problem=problem)
    total_votes = votes.count()
    yes_votes = votes.filter(is_this_done=True).count()
    percentage_yes = (yes_votes / total_votes) * 100
    return percentage_yes

Now for each property I am using similar method with difference being parameter passed to filter method. This certainly is not a DRY way of doing things.

I just want a single method where I can pass the parameter to pass to filter method.

Can you help or provide hints to achieve the same results the DRY way?

2 Answers 2

3

Firstly, methods like these that do queries on the model really belong on the Manager, not the model itself.

Secondly, you can use dictionary expansion to dynamically pass the field to filter against.

class ProblemVerificationManager(models.Manager):
    def percentage(self, problem, filter_keyword):
        votes = self.filter(problem=problem)
        total_votes = votes.count()
        yes_votes = votes.filter(**{filter_keyword: True}).count()
        percentage_yes = (yes_votes / total_votes) * 100
        return percentage_yes

class ProblemVerificationModel(CreateUpdateDateModel):
    ...
    objects = ProblemVerificationManager()

Now you would call this as ProblemVerificationModel.objects.percentage(problem, 'is_problem_properly_formatted').

In Django 2.0+, you can use aggregation to do return both the full count and the percentage that match in a single db query:

from django.db.models import Count, Q

class ProblemVerificationManager(models.Manager):
    def percentage(self, problem, filter_keyword):
        votes = self.filter(problem=problem).aggregate(
           yes_votes=Count(1, filter=Q(**{filter_keyword: True}),
           total_votes=Count(1)
        )
        percentage_yes = (votes['yes_votes'] / votes['total_votes']) * 100
        return percentage_yes
Sign up to request clarification or add additional context in comments.

Comments

1

Pass filter arguments as dict and use ** syntax to unpack it:

@classmethod
def percentage_is_this_done(cls, problem, filters):
    votes = cls.objects.filter(problem=problem)
    total_votes = votes.count()
    yes_votes = votes.filter(filters).count()
    percentage_yes = (yes_votes / total_votes) * 100
    return percentage_yes

to call this method:

MyClass.percentage_is_this_done(problem, {'is_this_done': True})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.