Django average scores with through - django

Maybe this is a very newbie question, but I am stuck in this point. I do not know if the problem is the model or I do not understand very well aggregations and annotations.
I have a model like this:
class User(models.Model):
collection = models.ManyToManyField(Book, through='BookCollection')
class Book(models.Model):
name = models.CharField(max_length=200)
class BookCollection(models.Model):
user = models.ForeignKey(User)
book = models.ForeignKey(Book)
score = models.IntegerField(default=0)
I want to get the score average for all the books and all users, excluding that ones that has a default score equals to 0 (this value represents that the user has the book in the collection, but it has not been rated). I am trying to use an annotation like this:
Book.objects.exclude(collection__score=0).annotate(avg=Avg('collection__score'))
but if there is a book rated with 0 and 3, for example, both entries are excluded.
Is there any way to tell Avg() that it should take into account only values greater than 0?
Thanks in advance.

There is no way to do that in the Django ORM without raw SQL.
A better model would be to allow null values in your score field. Null values are ignored in Avg():
class BookCollection(models.Model):
...
score = models.IntegerField(null=True, blank=True, default=None)
None is generally the best way to describe a lack of an entry in a field. This avoids confusion, e.g. in calculations such as calculating the average.

Related

How to find the highest value of a ForeignKey field for a specific user in Django

I am building an app that allows users to record their workouts. First they will create an exercise, (e.g Bench Press), and then they will complete a form to show how much weight they were able to lift for that specific exercise. Their results will display below the form. There will be many workout forms, relating to many different workouts. The workouts and exercises will also be specific to each user.
Here is my models.py:
from django.contrib.auth.models import User
class Exercise(models.Model):
name = models.CharField(max_length=100)
class Workout(models.Model):
user = models.ForeignKey(Profile, on_delete=models.CASCADE)
weight = models.DecimalField(default=0.0, max_digits=5, decimal_places=1)
exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE, default=None)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
What I now want to do is be able to show the user what their max lift was for each different workout, but can't figure out how to retrieve this information. I have searched for the answer online and it seems that using aggregate or annotate might be the way to go, but I have tried a bunch of different queries and can't get it to show what I need. Hope somebody can help.
You can annotate your Exercises with the maximum weight for the Workouts of a given user with:
from django.db.models import Max
Exercise.objects.filter(
workout__user=someprofile
).annotate(
max_weight=Max('workout__weight')
)
The Exercise objects that arise from this queryset will have an extra attribute .max_weight that contains the maximum weight for that exercise for someprofile.
Given a user you need to annotate each excersise with max weight.
from django.db.models import Max
user.workout_set.values('exercise').annotate(max_weight=Max('weight'))
this should work
Instead of using aggregation, an easy way of doing it is ordering and then using distinct on exercise:
Workout.objects.filter(user=user)\
.order_by('exercise', '-weight')\
.distinct('exercise')
This will give you a list of all workouts with the biggest weight for each exercise.

statistic with django

I am currently having the following
class Profile(models.Model):
gender = models.BooleanField()
class Q(models.Model):
name = models.CharField(max_length=128)
class A(models.Model):
q = models.ForeignKey(Q)
profile = models.ForeignKey(Profile)
What I try to do is to query Q and get the number of Answers given by either male or female?
Unfortunately, I don't even have a better idea than writing custom SQL, which I'd rather avoid for the sake of database portability. How would I start?
You're there. You just need to actually use the right filter syntax. If you're trying filter on the gender field on profile, then you do that with profile__gender, so:
males_answering = question.answers.filter(profile__gender="male").count()

Choosing best Django architecture for scalability / performance

I had a doubt on how to architecture the model.
I want to give some entities the possibility to be voted, in this case, a paper. I came up with this two possibilities:
Option 1:
Link the entity as a relationship
class Vote(model.Model):
author = models.ForeignKey(User)
created = models.DateField(auto_now=True)
value = models.IntegerField(default=1)
class Paper(models.Model):
author = models.ForeignKey(User)
edition = models.ForeignKey(ConferenceEdition)
votes = models.OneToMany(Vote)
advantages:
It's easier to work with the model (ORM)
I can use this vote entity with others
I may need this information when rendering the HTML, to show which papers the user has already voted.
Desavantages:
I'm afraid the largest the database, the slower it can get.
Option 2:
Not to link the class
class Vote(model.Model):
author = models.ForeignKey(User)
created = models.DateField(auto_now=True)
value = models.IntegerField(default=1)
entity_id = models.IntegerField()
entity_type = models.CharField(max_length=255,default='Paper')
class Paper(models.Model):
author = models.ForeignKey(User)
edition = models.ForeignKey(ConferenceEdition)
num_votes = models.IntegerField(default=0)
Avantages:
It's kind of a lazy loading, I have a counter and if I need the information I can go for it.
It's faster ( I think )
Desavantages:
You must rely on a new logic to update all the new votes.
Option 3:
I'm listening
Thanks!
Django loads many to many fields only if you explicitly call them.
So in your 1st case:
paper.votes.all()
If you want to load all the votes when doing your query, you can in django 1.4 do prefetch_related
paper = Paper.objects.get(pk=1).prefetch_related('votes')
By the way, instead of .all() you can use .count(), which generates a different database query that is much faster since it only has to count values, instead of retrieve them into django/python.
There is also a third approach:
You coud have extra field in your model: votes_count, that you would update on pre_save(), and it would hold that value for you. This way you get both: you can query for all votes, but you can also just grab a number.

Django ORM query instead of using a dictionary

I have the following models:
class ProjectUser(models.Model):
categories = models.ManyToManyField('UserCategory', blank=True, null=True)
user_id = models.PositiveIntegerField(db_index=True)
name = models.CharField(max_length=80)
actual_rank = models.FloatField(default=0)
class UserCategory(models.Model):
name = models.CharField(max_length=100)
What I'd like to do is get the categories name and the number of times they appear in the users divided by the length of another object.
I'm doing it creating a categories dictionary {'category': ocurrences } and then iterating it and dividing the ocurrences by a number to get the category rank.
Maybe this is a good way to do it, but I'd like to know if it could be done directly using some queryset methods. I'm doing similar things in a lot of places and finding a better and more succint way to solve it would be great.
This should get all users with the category name in their categories, count that number of users, then divide that number by the length you're getting somewhere else.
ProjectUser.objects.filter(categories__name__contains=category.name).count() / objectLength
category being the current one you're trying to analyse.

Showing partial tabular inline models in Django

Let's see if I can explain myself, I have this models:
class BillHeader(models.Model):
number = models.CharField(_('Bill number'), max_length=10, unique=True, \
default=__number)
client = models.ForeignKey(ClienteYProveedor, verbose_name=_('Client'))
date = models.DateTimeField(_('Date'), default=datetime.now)
def __unicode__(self):
return str(self.number)
class Meta:
abstract = True
class BillFooter(models.Model):
base_import = models.DecimalField(_('Base import'), max_digits=12, \
decimal_places=2)
class Meta:
abstract = True
class BillBody(models.Model):
description = models.CharField(_('Description'), max_length=200)
amount = models.DecimalField(_('Amount'), max_digits=6, decimal_places=2)
discount = models.DecimalField(_('Discount'), max_digits=4, \
decimal_places=2)
price = models.DecimalField(_('Price'), max_digits=12, decimal_places=2)
unitaryprice = models.DecimalField(_('Unitary Price'), max_digits=12, \
decimal_places=2)
def __unicode__(self):
return self.description
class Meta:
abstract = True
class EmittedBill(BillHeader, BillBody, BillFooter):
pass
class ReceivedBill(BillHeader, BillBody, BillFooter):
pass
When the user adds an Emmited or Received bill I need to show BillHeader as a normal fieldset, but BillBody and BillFooter need to be TabularInline.
If I put those as TabularInline in admin.py, an error rises saying that they need a ForeignKey to the related models. Of course, I can't put those foreign keys, because they are declared at the bottom. I think you guys call this "backwards foreign keys".
My question is this: how can I do this to show TabularInlines in the admin without making a mess?. I can do it without abstract base classes, but then another problem comes, it shows the other ForeignKey in the TabularInline (if you are on EmmitedBills it shows the FK to ReceivedBills in the TabularInline and viceversa) and I couldn't been able to exclude them.
Sorry if this is a stupid question, but I'm not a programmer (it's just a hobby) and I'm really making myself a mess with data models.
I'll explain better:
I have two types of bills, Emitted and Received and both of them show on the admin home (that's why I didn't use a BooleanField to mark them). Both types have the same fields except one, the bill number, which in Emmitted will be autogenerated. Each bill consists on 1 header with number, client and date, 1 or more body inline entries with a description, amount, price, etc. and 1 inline footer, showing the total price without taxes, applied taxes, etc.
Update
I have done everything, but I have a problem, in my new model BillBody has two FK's (EmmitedBill and ReceivedBill) and they show up in the TabularInline. How can I hide them?field.exclude() gives an error.
I don't fully understand your question, but you can use
ForeignKey('ModelName')
instead of
ForeignKey(ModelName)
if the ModelName model isn't already declared. Maybe this solves your problem.
Inline admins (like TabularInline) are only used when you have a one-to-many relation, which is created by a ForeignKey on the many side. If you don't have such a foreign key, then you cannot use a inline admin. Inheritance is definitely different from a ForeignKey.
However, I think your data model is wrong. It seems like you do want to store bills. There are two types of bills, emitted and received bills. Both emitted and received bills do have the same fields. Furthermore, you want that each bill consists of a header with number, client and date, 1 or more body entries, where each entry stores the information you store in BillBody and 1 or more decimals base_number.
A probably better data model for you
class Bill(models.Model):
number = models.CharField(_('Bill number'), max_length=10, unique=True, default=__number)
client = models.ForeignKey(ClienteYProveedor, verbose_name=_('Client'))
date = models.DateTimeField(_('Date'), default=datetime.now)
def get_total_price(self):
return sum([entry.price for entry in self.entries])
class BillEntry(models.Model):
bill = models.ForeignKey(Bill, related_name='entries')
description = models.CharField(_('Description'), max_length=200)
amount = models.DecimalField(_('Amount'), max_digits=6, decimal_places=2)
discount = models.DecimalField(_('Discount'), max_digits=4, decimal_places=2)
price = models.DecimalField(_('Price'), max_digits=12, decimal_places=2)
unitaryprice = models.DecimalField(_('Unitary Price'), max_digits=12, decimal_places=2)
I have left out the __unicode__ methods.
Now you have a foreign key from BillEntry to Bill and you can use a tabular inline. I didn't understand your usage of base_import so I left this out.
Price computation
If your price should always equal something like amount*unitaryprice - discount or amount*(unitaryprice-discount) then you shouldn't put this in a field but either compute it when it is needed, either in Python or in the database. If you want to do this in Python you can use a method similar to get_total_price. If you want to compute them when querying the database then it is a little bit more difficult to get it working with Django.
In the last case, you can have a look at SQL views, but I think this is a little bit too difficult for a beginner. Another option is to use a custom SQL expression:
BillEntry.objects.extra(select={'price': 'amount*unitaryprice-discount'})
This will compute the price for all entries during selection.
Update
If you add two subclasses for emitted and received bills and use multi table inheritance then you can use one foreign key from BillEntry to Bill.
class EmittedBill(Bill):
pass
class ReceivedBill(Bill):
pass
You probably also have to think about the database model generated by Django. Usually, you only want to store elementary data in the database, and not computed data (like you want to do in your footer). So, if prices are computed using some formula and using the unitaryprice, amount etc. you shouldn't store the result of this formula but recompute it when necessary (and eventually cache to avoid re-computations). If you don't do this, chances are that you at some moment update something (for example the amount) and forget to update the computed values (the price) which leads to inconsistencies in your database (and thus bugs in your application). A good database does have constraints so that it is impossible to store inconsistent database without breaking at least one constraint.
I also don't see why you want a separate header and footer per bill. A model isn't the real bill, it stores the information for a bill. If you want to have a visible header and footer, then you should do this in your view layer (the template) and not in the model itself.