tags:

views:

113

answers:

3

I dont know if this is the best way to resolve my problem, if isn't , tell me plz :)

I have this model :


class userTrophy(models.Model):
 user     = models.ForeignKey(userInfo)
 platinum = models.IntegerField()
 gold    = models.IntegerField()
 silver  = models.IntegerField()
 bronze  = models.IntegerField()
 level   = models.IntegerField()
 perc_level  = models.IntegerField()
 date_update = models.DateField(default=datetime.now, blank=True)

Now i want to retrieve one user info, but i want add 3 new "columns" online :

total = platinum + gold + silver + bronze

point = platinum * 100 + gold * 50 + silver * 25 + bronze * 10

and sort by "point", after sort, put a new column, with a sequencial number: rank (1-n).

Can i do this ( or part of this ) working only with the model ?

+3  A: 

I am sure there are many ways to achieve this behavior. The one I am thinking of right now is a Custom Model Manager and transient model fields.

Your class could look like so:

from django.db import models
from datetime import datetime

class UserTrophyManager(models.Manager):
  def get_query_set(self):
    query_set = super(UserTrophyManager, self).get_query_set()

    for ut in query_set:
      ut.total = ut.platinum + ut.gold + ut.silver + ut.bronze
      ut.points = ut.platinum * 100 + ut.gold * 50 + ut.silver * 25 + ut.bronze * 10

    return query_set

class UserTrophy(models.Model):
  user         = models.CharField(max_length=30)
  platinum     = models.IntegerField()
  gold         = models.IntegerField()
  silver       = models.IntegerField()
  bronze       = models.IntegerField()
  level        = models.IntegerField()
  perc_level   = models.IntegerField()
  date_update  = models.DateField(default=datetime.now, blank=True)

  total        = 0
  point        = 0

  objects      = UserTrophyManager()

  class Meta:
    ordering = ['points']

So you can use the following and get total and point calculated:

user_trophies = userTrophy.objects.all()

for user_trophy in user_trophies:
    print user_trophy.total
Jens
What are "transient model fields"?
Joe Holloway
In general there are two types of fields in a model. Persistent fields and transient fields. Persistent fields are represented by databases fields while transient fields only live as long the object lives. This means transient fields are only temporarily. This is not a "django term". This is more of a common term for this kind of fields.
Jens
In your example, total and points are class attributes. Does the Django metaclass do something special with them to turn them into instance level attributes?
Joe Holloway
No. In the moment you are calling userTrophy.objects.all() you would get a QuerySet with the instances from the custom model manager (UserTrophyManager). The returned instances would have the fields total and point set to 0 and you could work with them as if they would be normal instance attributes: "usertrophy.point = 10" for instance. But if you call "userTrophy.save()" they wouldn't be saved to the database. Of course you could already fill the values in the custom model manager. But the metaclass has nothing to do with this.
Jens
I updated my example code to a working version. I tested this locally and it worked. The only thing I changed is the user field which is for the sake of simplicity just a charfield.
Jens
How does the 'ordering' work? Doesn't this affect the ORDER BY clause in the generated SQL which doesn't know about 'points' since they're transient to the model object?
Joe Holloway
+1  A: 

This is more of a followup question than an answer, but is it possible to do something like:

class userTrophy(models.Model):
... stuff...

    def points(self):
        self.gold + self.silver + self.bronze

then call something like object.points in a template. Im just curious if that is a possibility

Dave
Of course it's possible, Django models are just Python classes and you can define any methods you like on them.
Daniel Roseman
The disadvantage to defining a method like this is that the ORM can't use it in queries.
Joe Holloway
+3  A: 

Here's the way I would do it. Add the columns 'total' and 'points' to your model, like this:

class UserTrophy(models.Model):
    ...
    total = models.IntegerField()
    points = models.IntegerField()
    ...

Override the save method for your model:

def save(self, *args, **kwargs):
    # Compute the total and points before saving
    self.total = self.platinum + self.gold + self.silver + self.bronze
    self.points = self.platinum * 100 + self.gold * 50 + \
        self.silver * 25 + self.bronze * 10

    # Now save the object by calling the super class
    super(UserTrophy, self).save(*args, **kwargs)

With total and points as first class citizens on your model, your concept of "rank" becomes just a matter of ordering and slicing the UserTrophy objects.

top_ten = UserTrophy.objects.order_by('-points')[:10]

You'll also want to make sure you have your fields indexed, so your queries are efficient.

If you don't like the idea of putting these fields in your model, you might be able to use the extra feature of Django query set objects to compute your total and points on the fly. I don't use this very often, so maybe someone else can put together an example.

Also, I recommend for you to read PEP 8 for Python coding conventions.

Joe Holloway
Looks like a nice way to do this! Maybe this is the better way since you only do to computing of the values once and not every time you load the object!
Jens
One downside to note is if you update your table outside of the Django ORM, you'll have to remember to keep your points and total updated as well.
Joe Holloway
thanks for the answer !just a question , why i need to read the PEP 8 ? I make some style mistake on my code ?
fabriciols
Just a few minor things... Your class name is 'userTrophy' whereas the convention is 'UserTrophy'. Indentation is conventionally four spaces where you have only one. Also, the gratuitous spacing before the '=' is a bit funky. PEP 8 isn't gospel, but following it more closely will help others to read your code more easily here on StackOverflow for sure.
Joe Holloway