I am developing a vocabulary training program with Django (German-Swedish).
The app's vocabulary data consists of a large number of "vocabulary cards", each of which contains one or more German words or terms that correspond to one or more Swedish terms.
Training is only available for registered users, because the app keeps track of the user's performance by saving a score
for each vocabulary card.
Vocabulary cards have a level (basic, advanced, expert) and any number of tags assigned to them.
When a registered user starts a training, the application needs to calculate the user's average scores for each of the levels and tags, so he can make his selection.
I have solved this problem by introducing a model named CardByUser
that has a score
and field and ForeignKey
relationships to the models User
and Card
. Now I can use Django's aggregation function calculate the average scores.
The big disadvantage: this works only if there is a CardByUser
instance for each and every Card instance that currently exists in the DB, even if the user has only trained 100 cards. My current solution is to create all those CardByUser
instances on Card
creation and when a user is registered. This is, of course, rather ineficient both in terms of data base memory and of computing time (registering a user takes quite a while).
And it seems quite inelegant, which kind of bugs me the most.
Is there a better way to do this?
Maybe it is possible to tell Django the following when calculating the average score for a Card
:
CardByUser
for the given Card
and User exists, use its score. CardByUser
doesn't exist, use a default value --> the score 0. Can this be done? If so, how?
Edit: Clarification Thanks S.Lott's for the first answer, but I think that my problem is a bit more complicated. My bad, I'm trying to clarify using some actual code from my models.
class Card(models.Model):
entry_sv = models.CharField(max_length=200)
entry_de = models.CharField(max_length=200)
... more fields ...
class CardByUser(models.Model):
user = models.ForeignKey(User)
card = models.ForeignKey(Card, related_name="user_cards")
score = models.IntegerField(default=0)
... more fields ...
This means many CardByUser
objects are related to a single Card
.
Now in my view code, I need to create a queryset of CardByUser
objects that fulfill the following criteria:
Card
object's tag
field contains a certain string (I now that's not optimal either, but not the focus of my question...) Then I can aggregate over the scores. My current code looks like this (shortened) :
user_cards = CardByUser.objects.filter(user=current_user)
.filter(card__tags__contains=tag.name)
avg = user_cards_agg.aggregate(Avg('score'))['score__avg']
If a CardByUser
for the current user and Card
does not exist, it will simply not be included in the aggregation. That's why I create all those CardByUser
s with a score of 0.
So how could I get rid of those? Any ideas would be appreciated!
This is what methods (and perhaps properties) are for.
class OptionalFKWithDefault( models.Model ):
another = models.ForeignKey( AnotherModel, blank=True, null=True )
@property
def another_score( self ):
if self.another is None:
return 0
else:
return self.another.score
This may not be entirely related to your question, but it looks like CardByUser
really should be a many-to-many relationship with an extra field. (see http://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships )
Maybe you could alter your model this way?
class Card(models.Model):
entry_sv = models.CharField(max_length=200)
entry_de = models.CharField(max_length=200)
... more fields ...
users = models.ManyToManyField(User, through='CardByUser')
class CardByUser(models.Model):
user = models.ForeignKey(User)
card = models.ForeignKey(Card)
score = models.IntegerField(default=0)
Then you won't have to explicitely create CardByUser objects, as this is all taken care of by Django. You should be able to simplify your aggregation query as well:
user_cards = Card.objects.filter(users=current_user)
.filter(tags__contains=tag.name)
...
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.