How do I create a dynamic field on a model?
Let's say I'm writing an application related to the stock market. I make a purchase on one day and sometime later I want to check the gain (or loss) based on today's price. I'd have a model like this:
class Purchase(models.Model):
ticker = models.CharField(max_length=5)
date = models.DateField()
price = models.DecimalField(max_digits=20, decimal_places=3)
quantity = models.IntegerField()
What I'd like to do is define a model something like this:
class PurchaseGain(Purchase):
gain = models.DecimalField(max_digits=20, decimal_places=3)
class Meta:
proxy = True
So that I could do this:
todays_price = get_price_from_webservice(ticker)
for p in PurchaseGain.objects.get_purchase_gain(todays_price):
print '%s bought on %s for a gain of %s' % (p.ticker, p.date, p.gain)
Where p.gain is dynamically computed based on the input to get_purchase_gain. Rather than just constructing dictionaries on the fly I want to use a model, because I'd like to pass this around and generate forms, save changes, etc from the instance.
I tried creating a derived QuerySet, but that led to a circular dependency, because Purchase needed to know about the QuerySet (through a custom manager) and the QuerySet returned an iterator that needed to instantiate a PurchaseGain, which was derived from Purchase.
What options do I have?
Thanks,
Craig
Why not add a gain() method to your model?
class Purchase(models.Model):
ticker = models.CharField(max_length=5)
date = models.DateField()
price = models.DecimalField(max_digits=20, decimal_places=3)
quantity = models.IntegerField()
def gain(self, todays_price=None):
if not todays_price:
todays_price = get_price_from_webservice(self.ticker)
result_gain = todays_price - self.price
return result_gain
Then you can pretty much do what you want:
for p in Purchase.objects.all():
print '%s bought on %s for a gain of %s' % (p.ticker, p.date, p.gain())
Creating a proxy class is what confused me. By just adding attributes to a Purchase, I was able to accomplish what I wanted.
class PurchaseQuerySet(QuerySet):
def __init__(self, *args, **kwargs):
super(PurchaseQuerySet, self).__init__(*args, **kwargs)
self.todays_price = None
def get_with_todays_price(self, todays_price):
self.todays_price = todays_price
cloned = self.all()
cloned.todays_price = todays_price
return cloned
def iterator(self):
for p in super(PurchaseQuerySet, self).iterator():
p.todays_price = self.todays_price
yield p
class PurchaseManager(models.Manager):
def get_query_set(self):
return PurchaseQuerySet(self.model)
def __getattr__(self, name)
return getattr(self.get_query_set(), name)
class Purchase(models.Model):
ticker = models.CharField(max_length=5)
date = models.DateField()
price = models.DecimalField(max_digits=20, decimal_places=3)
quantity = models.IntegerField()
objects = PurchaseManager()
#property
def gain(self):
return self.todays_price - self.price
Now I can do:
for p in Purchase.objects.filter(ticker=ticker).get_with_todays_price(100):
print p
print p.gain
Related
I'm trying to create a new table (Payslip model) that will contain the computed salary on an employee in a cutoff.
I want Payslip.salary to get value from Employee.compute_pay()
Given the example in the url link above, what should my views.py look like?
Is this the best approach to this kind of process? or Is there any library that can help with what I want to do here?
https://imgur.com/a/wVG5qrd
model.py
class Employee(models.Model):
name = models.CharField(max_length=20)
rate = models.IntegerField()
absent = models.IntegerField()
def __str__(self):
return self.name
def compute_pay(self):
daily = rate / 20
return rate - (daily*absent)
class Payslip(models.Model):
name = models.CharField(max_length=20)
salary = models.IntegerField()
def __str__(self):
return self.name
views.py
def compute(request):
if request.method == "POST":
return render(request,'payroll/compute/compute.html')
I do not think there is a need for another model Payslip, also you have no ForeignKey connections between the two models for it to work.
Considering your requirement, property decorator should work. Read up on how #property works. Basically, it acts as a pseudo model field, keep in mind the value of this field is not stored anywhere in the database, but it is tailor-made for situations like this where the field changes based on other fields and is needed for read-only.
Try this
class Employee(models.Model):
name = models.CharField(max_length=20)
rate = models.IntegerField()
absent = models.IntegerField()
def __str__(self):
return self.name
#property
def compute_pay(self):
daily = self.rate / 20
return (self.rate - (daily*self.absent))
you can get the employee's salary by Employee.compute_pay just like any other model field
these are my models
class Countries(models.Model):
Name = models.CharField(max_length=100)
Language = models.IntegerField()
Population = models.IntegerField(default=0)
class World(models.Model):
Languages_spoken = model.Charfield(max_length=12000)
World_population = models.IntegerField(default=0)
I am trying to add Population of all instances in Countries to sum and show on World_population field on class World
What I have tried
class World(models.Model):
Languages_spoken = model.Charfield(max_length=12000)
World_population = models.IntegerField(default=0)
def save(self, *args, **kwargs):
self.World_population = Countries.objects.get(Population) # I know this is not correct
super(World,self).save()
Give this a shot:
from django.db.models import Sum
Countries.objects.aggregate(total_population=Sum('Population'))
More info on aggregation here
You can use Sum() here,
self.World_population = Countries.objects.aggregate(Sum('Population'))
https://docs.djangoproject.com/en/2.1/topics/db/aggregation/
I'm trying to return a queryset of all items in a Category where items can occur in multiple categories. The relevant model declarations are below along with one of many attempts that did not work. Is there a way to do this using Django's built-in intermediate table functionality without having to explicitly declare a model for the intermediary table?
class Category(models.Model):
parent = models.ForeignKey('self',null=True,blank=True)
name = models.CharField(max_length=150,null=True)
description = models.CharField(max_length=255,null=True,blank=True)
def items(self):
curr_category = Category.objects.filter(pk=self.id)
items_in_category = curr_category.item__categories_set.all().values('item_id')
return Item.objects.filter(pk__in=items_in_category)
def __unicode__(self):
return self.name
class Item(models.Model):
name = models.TextField(null=True,blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2,null=True,blank=True)
categories = models.ManyToManyField(Category,null=True)
One way you could do this is with a custom models.Manager on your Item model. This is ideal, IMHO, because this logic doesn't really belong in your Category model. What if you want categories for things besides Item's? Then you'd have to implement more retrieval methods on Category, bloating it.
class Category(models.Model):
parent = models.ForeignKey('self',null=True,blank=True)
name = models.CharField(max_length=150,null=True)
description = models.CharField(max_length=255,null=True,blank=True)
def __unicode__(self):
return self.name
class ItemManager(models.Manager):
def get_for_category(self, category):
return self.filter(categories=category)
class Item(models.Model):
name = models.TextField(null=True,blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2,null=True,blank=True)
categories = models.ManyToManyField(Category,null=True)
objects = ItemManager()
Then call this using:
items = Item.objects.get_for_category(category_instance)
If you really want to do it in a Category method, then why not:
class Category(models.Model):
def items(self):
# probably need to import Item model here in order to avoid
# circular import reference
from myapp.models import Item
return Item.objects.filter(categories__id=self.id)
Currently my models are:
class Workout(models.Model):
date = models.DateField()
routine = models.ForeignKey('Routine')
def __str__(self):
return '%s' % self.date
class Routine(models.Model):
name = models.CharField(max_length=255)
exercises = models.ManyToManyField('Exercise')
def __str__(self):
return self.name
class Exercise(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
I want the user to be able to create a new entry specified by a date(Workout). They can also create routines(Routine), associated with the date and filled with different exercises(Exercise) which they can also create.
Here is the part I can't figure out.
I want the user, when adding a new exercise, to be able to choose whether it is a strength exercise or cardio exercise. Strength exercises will have fields like: #of sets, reps, and weight. Where as carido will have fields like length and speed.
I am unclear on how to relate the two types of exercises to the Exercise class.
The most common way of doing this, is to create a generic relationship, such as:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Exercise(models.Model):
name = models.CharField(max_length=255)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
info = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.name
class StrengthExercise(models.Model):
sets, reps, weight = (...)
class CardioExercise(models.Model):
length, speed = (...)
Example use:
>>> from app_name.models import Exercise, CardioExercise
>>> exercise_info = CardioExercise.objects.create(length=600, speed=50)
>>> exercise = Exercise(name="cardio_exercise_1", info=exercise_info)
>>> exercise.save()
>>> exercise.info.length
600
>>> exercise.info.__class__.__name__
'CardioExercise'
OBS: Make sure you have 'django.contrib.contenttypes' in your INSTALLED_APPS (enabled by default).
I have two models with ManyToMany relation. Here is the code:
class Player(models.Model):
first_name = models.CharField(max_length = 30, verbose_name = u"First name")
last_name = models.CharField(max_length = 50, verbose_name = u"Last name")
def __unicode__(self):
return "%s %s" % (self.last_name, self.first_name)
class Tournament(models.Model):
title = models.CharField(max_length = 100, verbose_name = u"Tournament's title")
players = models.ManyToManyField(Player,verbose_name = u"Tournament's players")
def __unicode__(self):
return self.title
def save(self, **kwargs):
Tournament(title = self.title)
all_players = Player.objects.all()
for member in all_players:
member_of_tournament = member.tournament_set.filter(title = self.title)
for j in member_of_tournament:
print member.tournament_set.filter(title = self.title)
self.players.add(member)
super(Tournament, self).save(**kwargs)
When I save Tournament for the first time it saves only the title. But when I save next time it saves players too and relates them to the Tournament. How can I save them at the same time with the tournament?
I think you have a number issues here:
class Tournament(models.Model):
title = models.CharField(max_length = 100, verbose_name = u"Tournament's title")
players = models.ManyToManyField(Player,verbose_name = u"Tournament's players")
def __unicode__(self):
return self.title
def save(self, **kwargs):
Tournament(title = self.title)
all_players = Player.objects.all()
for member in all_players:
member_of_tournament = member.tournament_set.filter(title = self.title)
for j in member_of_tournament:
print member.tournament_set.filter(title = self.title)
self.players.add(member)
super(Tournament, self).save(**kwargs)
In general, you don't want to have your m2m relationships in the model save method (and in this case, the logic for it isn't very good in any case)
You have a few issues in the save method itself, so let me address those:
def save(self, **kwargs):
Tournament(title = self.title)
The last line above is doing nothing. You instantiate an instance of Tournament, but don't save it to a variable. You actually already have an instance of tournament anyway (called self, if this case).
all_players = Player.objects.all()
for member in all_players:
member_of_tournament = member.tournament_set.filter(title = self.title)
Here, you loop through all the players in the DB, whether they match your query or not.
This is really inefficient.
In the next line, you have member_of_tournament = member.tournament_set.filter(title = self.title). This is plural, so you should call this members_of_tournament because this is an array / list / queryset.
I'm honestly not sure what the strategy is of the rest, but suffice to say, you shouldn't do it like that, probably.
You should just get ride of the entire custom save method and in your view that is driving this, you should do something like this:
tournament = Tournament(title=title)
tournament.save()
players_i_care_about = [players, go, here]
tournament.players = players_i_care_about #(removes all players and saves new players)
or
for player in players_i_care_about:
tournament.players.add(player) #(only adds these players, doesn't remove any players)
The reason for this, your view knows about what players belong to what tournament, but your model should be agnostic to that logic.