Django: Dynamic model field definition - django

Hi Stackoverflow people,
I have a model definition which is rather monotone, it includes fields for bins from 1 to 50.
For the proof of concept, I wrote it by hand, but there must be a better way to automate the model definition and to keep the code nice and tidy.
So far I did it the follow way:
class Bins(models.Model):
p1 = models.DecimalField(_('bin 1'), max_digits=6, decimal_places=2)
p2 = models.DecimalField(_('bin 2'), max_digits=6, decimal_places=2)
p3 = models.DecimalField(_('bin 3'), max_digits=6, decimal_places=2)
...
p50 = ...
On the Django wiki, I found a summary for dynamic model definitions, but its seems that it does not allow for loops in the definition:
I have tried the code below, but I get an error msg that MAX_BIN = 2 is an invalid syntax.
I understand the error that I can't iterated over the field like I tried.
Bins = type('Bins', (models.Model,), {
MAX_BIN = 50
for i in range(MAX_BIN):
name_sects = ["p", str(i)]
"".join(name_sects): model.DecimalField(_("".join([bin ', str(i)])),
max_digits=6, decimal_places=2)
})
Is such a dynamic class definition generally possible?
If so, what is the best way to define the class?
Thank you for your help!

in your current edition, your loop is inside the definition of a dict. that's not allowed. however, you could define your fields in a dict outside your call to type which works fine. something like
attrs = {
other_field = models.IntegerField(),
'__module__': 'myapp.models',
}
MAX_BIN = 50
for i in range(MAX_BIN):
name_sects = ["p", str(i)]
attrs["".join(name_sects)] = model.DecimalField(_("".join(['bin ', str(i)])),
max_digits=6, decimal_places=2)
Bins = type('Bins', (models.Model,), attrs)

A simple solution that solves the same issue:
class Bins(models.Model):
MAX_BIN = 50
for i in xrange(1, MAX_BIN + 1):
vars()['p' + str(i)] = models.DecimalField('bin ' + str(i),
max_digits=6, decimal_places=2)

Related

how to access to property in view(django)

I'm beginner and I need some solution
First, I have Racket and Detail class.
class Racket(models.Model):
name = models.CharField(max_length=20)
class RacketDetail(models.Model):
racket = models.OneToOneField(Racket, related_name='detail', on_delete=models.CASCADE)
adminReview = models.TextField()
adminPower = models.FloatField(default=0)
adminSpin = models.FloatField(default=0)
adminManeuverability = models.FloatField(default=0)
adminStability = models.FloatField(default=0)
adminComfort = models.FloatField(default=0)
#property
def adminAvgScore(self):
scoreAvg = (
self.adminPower +
self.adminSpin +
self.adminManeuverability +
self.adminStability +
self.adminComfort
) / 5
return round(scoreAvg, 2)
Second, I want to rander list using the #property(adminAvgScore), so I made view like this.
def racketMain(request: HttpRequest):
getRacket = Racket.objects.all().order_by('detail__adminAvgScore')
return render(request, "racket/racketMain.html", {'racketItems': getRacket, })
Unfortunally when I use 'RacketDetail' class's column I can access all column except 'adminAvgScore' using by "order_by('detail__".
If I use like "order_by('detail__adminAvgScore')" then Django show to me error "Unsupported lookup 'adminAvgScore' for BigAutoField or join on the field not permitted."
How can I solve it? Or should I think in a different way?
You cannot use property with Query as Property is Function. You can use annotate and aggregate combination to get the result as your property function but inside a query.Something like this will do the trick.
from django.db.models import F, Sum, FloatField, Avg
Model.objects.filter(...)\
.values('id')\
.annotate(subtotal=Sum(...math here..., output_field=FloatField()))\
.aggregate(total=Avg(F('subtotal')))

Django aggregate with associated values

I have a models.py like so:
class ScoreCard(models.Model):
user = models.ForeignKey(User)
course = models.ForeignKey(Course)
created = models.DateTimeField(default=datetime.now)
score = models.IntegerField()
holes = models.IntegerField(
default=9
)
handicap = models.IntegerField(default=0)
class UserProfile(User):
...
def get_nine_stats(self, course):
nine_stats = ScoreCard.objects.filter(course=course) \
.filter(holes=9) \
.aggregate(Avg('score'), Max('score'), Min('score'))
return nine_stats
This is fine and returns almost what I need - a dict of max, min and avg. However, I really want those values with the associated created fields. Is this possible using the aggregate method?

Django model field relating to a few models

Imagine a 5x5 grid (map), every field of it represents a certain object (it can be a monster, a tree etc.)
So, here we have:
class Field(Model):
x = y = PositiveIntegerField()
content = ...(?)
Here the problem arises. Here is the alternative, but I think this way is too messy, especially if I have many different content ids.
class Field(Model):
x = y = PositiveIntegerField()
content = PositiveIntegerField()
monster_rel = ForeignKey(Monster, null=True, blank=True)
building_rel = ForeignKey(Monster, null=True, blank=True)
nature_obj_rel = ForeignKey(Monster, null=True, blank=True)
and then in a view:
f = Field.objects.get(pk=1)
if f.content == 1:
print "Monster %s of level %d" % (f.monster_rel.name, f.monster_rel.level)
elif f.content == 2:
print "This is a %s" % f.building_rel.type
...
Is there a better solution for this?
EDIT
I would like fields like:
content_id = IntegerField()
content_rel = FieldRelatedToModelByContentId()
Well, sounds like generic relations is exactly what you're looking for.

Django - Add online columns in "select"

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 ?
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
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.
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

Custom save method giving invalid tuple size error

I've been stuck on this likely very simple problem, but haven't gotten anywhere with it (newbie to Python and Django). I'm taking some user submitted data and using weights to calculate a score. Despite my best efforts, I'm getting the following when I submit the data via a form: "global name 'appearance' is not defined". I'm pretty sure my issue is in views.py, but I'm not 100% sure. Either a typecast error or just putting the calculation of the score in the wrong place. Any help is much appreciated. Here's my code:
Update: The error I'm receiving after changing my approach to using a custom save method is: "Invalid tuple size in creation of Decimal from list or tuple. The list or tuple should have exactly three elements.".
models.py
# Beer rating weights
APPEARANCE_WEIGHT = 0.15
AROMA_WEIGHT = 0.15
MOUTHFEEL_WEIGHT = 0.10
TASTE_WEIGHT = 0.25
TOTALPACKAGE_WEIGHT = 0.25
SERVING_TYPE = (
('0', 'Choose One'),
('Draft', 'Draft'),
('Bottle', 'Bottle'),
('Can', 'Can'),
)
SCORING = (
(0, ''),
(1, '1'),
(2, '2'),
(3, '3'),
(4, '4'),
(5, '5'),
(6, '6'),
(7, '7'),
(8, '8'),
(9, '9'),
(10, '10'),
)
class Beerrating(models.Model):
beerrated = models.ForeignKey(Beer)
user = models.ForeignKey(User)
date = models.DateTimeField(auto_now_add=True)
servingtype = models.CharField(max_length=10, choices=SERVING_TYPE)
appearance = models.IntegerField(choices=SCORING, default=0)
aroma = models.IntegerField(choices=SCORING, default=0)
mouthfeel = models.IntegerField(choices=SCORING, default=0)
taste = models.IntegerField(choices=SCORING, default=0)
totalpackage = models.IntegerField(choices=SCORING, default=0)
comments = models.TextField()
overallrating = models.DecimalField(max_digits=4, decimal_places=2)
def __unicode__(self):
return u'%s, %s' % (self.user.username, self.beerrated.beername)
def save(self):
if not self.id:
scoredappearance = self.appearance * APPEARANCE_WEIGHT,
scoredaroma = self.aroma * AROMA_WEIGHT,
scoredmouthfeel = self.mouthfeel * MOUTHFEEL_WEIGHT,
scoredtaste = self.taste * TASTE_WEIGHT,
scoredtotalpackage = self.totalpackage * TOTALPACKAGE_WEIGHT,
self.overallrating = (scoredappearance + scoredaroma +
scoredmouthfeel + scoredtaste + scoredtotalpackage)
super(Beerrating, self).save()
forms.py
class BeerReviewForm(ModelForm):
servingtype = forms.CharField(max_length=10,
label=u'Serving Type',
widget=forms.Select(choices=SERVING_TYPE)
)
totalpackage = forms.IntegerField(
label=u'Total Package',
widget=forms.Select(choices=SCORING)
)
class Meta:
model = Beerrating
exclude = ('beerrated', 'user', 'date', 'overallrating')
views.py
def beerreview(request, beer_id):
beer = get_object_or_404(Beer, id=beer_id)
if request.method == 'POST':
form = BeerReviewForm(request.POST)
if form.is_valid():
# Create review
beerrating = Beerrating(
beerrated = beer,
user = request.user,
servingtype = form.cleaned_data['servingtype'],
appearance = form.cleaned_data['appearance'],
scoredappearance = appearance * APPEARANCE_WEIGHT,
aroma = form.cleaned_data['aroma'],
scoredaroma = aroma * AROMA_WEIGHT,
mouthfeel = form.cleaned_data['mouthfeel'],
scoredmouthfeel = mouthfeel * MOUTHFEEL_WEIGHT,
taste = form.cleaned_data['taste'],
scoredtaste = taste * TASTE_WEIGHT,
totalpackage = form.cleaned_data['totalpackage'],
scoredtotalpackage = totalpackage * TOTALPACKAGE_WEIGHT,
comments = form.cleaned_data['comments'],
)
beerrating.save()
return HttpResponseRedirect('/beers/')
else:
form = BeerReviewForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('beer_review.html', variables)
The error message should specifically tell you the file and line number of the error, but your problem are these two lines in your views.py:
appearance = form.cleaned_data['appearance'],
scoredappearance = appearance * APPEARANCE_WEIGHT,
You are assuming the Python interpreter computes the value for appearance before you use it in the next argument... which is an incorrect assumption.
Define appearance before you create the model instance and your code should then work (or at least break on a different error).
In your save method, the lines:
scoredappearance = self.appearance * APPEARANCE_WEIGHT,
...
are all assigning a tuple, not the number you expect, to the variables. A tuple is basically an immutable list. The training comma on all those lines makes them tuples. What you want is:
scoredappearance = self.appearance * APPEARANCE_WEIGHT
...
Two other problems with your save function. First, because of your indentation, your super only gets called on an update -- which means you'll never be able to create this object!
Secondly, I'd recommend adding the variable arg lists to your save function. This means if it gets called with parameters, they get transparently passed onto the super.
Here's the rewritten function:
def save(self,*args,**kwargs):
if not self.id:
scoredappearance = self.appearance * APPEARANCE_WEIGHT
scoredaroma = self.aroma * AROMA_WEIGHT
scoredmouthfeel = self.mouthfeel * MOUTHFEEL_WEIGHT
scoredtaste = self.taste * TASTE_WEIGHT
scoredtotalpackage = self.totalpackage * TOTALPACKAGE_WEIGHT
self.overallrating = (scoredappearance + scoredaroma +
scoredmouthfeel + scoredtaste + scoredtotalpackage)
super(Beerrating, self).save(*args,**kwargs)
As a final note -- and if you've already done this, I apologize -- I'd really recommending working through a book like Learning Python. Python's a generally straightforward language -- but it has some subtle features (like tuples and variable argument lists) that will cause you trouble if you don't understand them.