Reputation: 791
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
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
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