inquilabee
inquilabee

Reputation: 791

pass class variable name to class method

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?

Upvotes: 1

Views: 98

Answers (2)

Daniel Roseman
Daniel Roseman

Reputation: 600041

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

Upvotes: 3

neverwalkaloner
neverwalkaloner

Reputation: 47374

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})

Upvotes: 1

Related Questions