Using property in Django - django

I need help with the following situation.
My app has the following models:
class Person(models.Model):
person_sequential_nr = models.IntegerField(primary_key=true)
person_id = models.CharField(max_length=10)
person_name = models.CharField(max_length=200)
year = models.CharField(max_length=4)
#property
def _summarize_goods(self):
return self.goods_set.all().aggregate(Sum('good_value')).values()
patrimony = property(_summarize_goods)
class Goods:
person_sequential_nr = models.Foreignkey()
good_description = models.CharField(max_length=200)
good_value = models.DecimalField(max_digits=12, decimal_places=2)
year = models.CharField(max_length=4)
Year is a string like 2012, 2010, 2008, etc
person_sequential_nr is specific (different) for each year.
person_id is the same for all years.
The intention of _summarize_goods is to totalize all goods of a person in a specific year.
1) How can I get the top ten people with the highest patrimonies?
When I call Person.patrimony it says "TypeError: 'property' object is not callable"
Person._summarize_goods works, but I have no idea how to order it.
2) How can I calculate the patrimony variation from of a person from one year to another (in the past)?
I would like to have something like: variation = (patrimony(year='2012')/patrimony(year='2010') - 1) * 100
I suppose that variation should be also a property, because I would like to use it to order some records.
An additional problem is the the person data may exist in 2012, but may not exist in a year in the past (e.g. 2010). So I need to handle this situation.
3) How can I create a view to show the patrimony of a person?
self.goods_set.all().aggregate(Sum('good_value')) was returning a dictionary, so I added .values() to extract only the values of it, then I got a list.
But wen I use str(Person._summarize_goods) it seemns that I still have a list.
Because when I call:
all_people = Person.objects.all()
people_list = [[p.person_name, str(p._summarize_goods)] for p in all_people]
output = ','.join(people_list)
return HttpResponse(output)
It shows an error referring the line output =
TypeError at /
sequence item 0: expected string, list found
EDITING...
Find some answers:
After removing the decoration (thanks Daniel) Person.patrimony is working so:
1) How can I get the top ten people with the highest patrimonies?
This was solved with the code below:
def ten_highest_pat():
people_2012 = Person.objects.all().filter(year=2012)
return sorted(people_2012, key=lambda person: person.patrimony, reverse=True)[:10]
2) How can I calculate the patrimony variation from of a person from one year to another (in the past)?
I tried the code below, which works, but is too slow. So I would thank you if someone has a sugestion how can I improve it.
def _calc_pat_variation(self):
c = Person.objects.all().filter(person_id = self.person_id, year__lt=self.year).order_by('-year')
if c.count() >= 1 and c[0].patrimony != 0:
return ((self.patrimony / c[0].patrimony) - 1) * 100
else:
return 'Not available'
pat_variation = property(_calc_pat_variation)

Related

How to display remaining days and hours in django from DateTimeField?

Here I have a simple function for sending leave request and accepting by the admin.This code works for now but I want to add some feature here.For example if the user enter day = 2 which is IntegerField then it get stores into databse then after the leave has been accepted by the function below def accept_leave(request,pk): I want to display the remaining days of leave(Example:1 day 12 hours and 30 sec. remaining to complete leave ).After 2 days completed it should display some message like you leave has been completed.
I got no idea for starting this .How can I do it ?Any help would be great.
Is there any mistake in my approach ?
EDIT: Now I removed the day(Integer Field) and added start_day and end_day as DateTimeField. Now how can I display the remaining days and time of leave after the leave is accepted ?
models.py
class Leave(models.Model):
staff = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,related_name='staff_leave')
organization = models.ForeignKey(Organization,on_delete=models.CASCADE,related_name='staff_leave')
sub = models.CharField(max_length=300)
msg = models.TextField()
start_day = models.DateTimeField()
end_day = models.DateTimeField()
#day = models.IntegerField(default=0)
is_accepted = models.BooleanField(default=False)
is_rejected = models.BooleanField(default=False)
sent_on = models.DateTimeField(auto_now_add=True)
views.py
def send_leave_request(request):
form = MakeLeaveForm()
if request.method == 'POST':
form = MakeLeaveForm(request.POST)
if form.is_valid():
leave_days = form.cleaned_data['day']
org = form.cleaned_data['organization']
start_day = form.cleaned_data['start_day']
end_day = form.cleaned_data['end_day']
diff = end_day - start_day
leave_days = diff.days
print('org',org)
if leave_days > request.user.staff.organization.max_leave_days:
messages.error(request,'Sorry can not be sent.Your leave days is greater than {}.'.format(request.user.staff.organization.max_leave_days))
return redirect('organization:view_leaves')
else:
leave = form.save(commit=False)
leave.staff = request.user
leave.organization = org
leave.save()
return redirect('organization:view_leaves')
return render(request,'organization/send_leaves.html',{'form':form})
def accept_leave(request,pk):
leave = get_object_or_404(Leave, pk=pk)
leave.is_accepted = True
leave.is_rejected = False
leave.day = ?
leave.save()
return redirect('organization:leave_detail',leave.pk)
For your leave request, why don't you store something like :
start_date and end_date
The idea is (ideally) to store only things you can't compute.
Then you can make a python property that computes the number of days between start_date and end_date (a "fget" would be enough for the property). A python property won't be stored in your database but it's not a big deal because you can compute it ! So you don't have to store it.
days = property(fget=_get_days, doc="type: Integer")
That means whenever the "days" attribute of an object "Leave" is accessed, the function "_get_days" is called to retrieve what you want.
If self represents a Leave object and you do print(self.days) it will print the result of _get_days finally.
The "doc" part is just here to indicate your property returns an Integer. It is not mandatory but a good practice in order not to forget it.
Then you must write that method "_get_days" (it must be above your property definition or Python won't know what is "_get_days"
def _get_days(self):
return self.end_date - self.start_date
(something like that, that you convert into an int somehow)
Moreover, for your additional functionality, you must know how much leaves your user can have. Just store that on the user, on your user team or whatever you want.
Then to check if the user has remaining leaves he can take, just browse a queryset with all his accepted leaves and use the property mentioned above.
Then you substract the result to the total number of leaves the user can take.

Error sum in sql request

I have model:
class Sales(ModelWithCreator, models.Model):
date = models.DateField(default=datetime.date.today)
merchandise = models.ForeignKey(Merchandise)
partner = models.ForeignKey(Partner)
count = models.PositiveIntegerField()
debt = models.PositiveIntegerField(default=0)
price = models.PositiveIntegerField()
cost = models.PositiveIntegerField()
I have to get sum of all objects that model.
I tried that:
Sales.objects.all().extra(select={
'total': 'Sum(price * count - cost)'
})
But, I got error:
column "sales_sales.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT (Sum(price * count - cost)) AS "total", "sales_sales"...
I think you might want to use the .aggregate() function. See the Django documentation for Aggregation.
Something like this (though I haven't tested it):
Sales.objects.all().aggregate( total = Sum((F(price)*F(count)) - F(cost)) )
Also, that same documentation suggests using the .query() function to see what the resulting query is. That might help you see what is going wrong.
Try this
Sales.objects.all().extra(select={
'total': 'Sum(price * count - cost)'
}).values("total")

Annotate number of identical items in manager

Let's say I have following simplified model:
class CurrentInvoices(models.Manager):
def get_queryset(self):
qs = super(CurrentInvoices, self).get_queryset()
current_invoices = qs.order_by('person', '-created_on').distinct('person').values('pk')
return qs.annotate(invoice_count=models.Count('number')).filter(id__in=current_invoices).order_by('person__last_name')
class Invoice(models.Model):
created_on = models.DateField()
person = models.ForeignKey(Person)
total_amount = models.DecimalField()
number = models.PositiveSmallIntegerField()
objects = models.Manager()
current_invoices = CurrentEDCInvoices()
A Person can have an Invoice with the same number if for some reason the previously generated invoice was wrong. The latest one (highest created_on) is the one that counts.
The trick with .filter(id__in) in the manager is needed to get the results listed by persons last name; this cannot be removed.
Now I'd like to annotate the total count of number.
My try annotate(invoice_count=models.Count('number')) always returns 1 even though there are multiple.
What am I doing wrong? Any pointers on how to properly achieve this without hacking around too much and without hitting the DB for every invoice?
Seems your problem in distinct('person'), which removes duplicates by person field.
Update
To complete your task you should
current_invoices = qs.order_by('person', '-created_on').distinct('person').values('number')
return qs.annotate(invoice_count=models.Count('number')).filter(number__in=current_invoices).order_by('person__last_name')

which is best ForeignKey or choices ? what is the different?

I want Create Car Advertising website
and I have many list like year,brand and status
which is the best use Category OR choices with list
and Taking into account the I wanna make extended search engine
see code for tow methods
YEARS = (
("1990", "1990"),
("1991", "1991"),
("1992", "1992"),
.
.
.
.
("2013", "2013"),
)
class Whatever(models.Model):
# Show a list with years
birthdate = models.IntegerField(max_length=2, choices=YEARS)
#OR this method
class ChoiceYears(models.Model):
type = models.CharField(max_length=60)
def __unicode__(self):
return '%s' % self.typeclass Adv(models.Model):
class Adv(models.Model):
years = models.ForeignKey(ChoiceYears)
and this
class ChoiceStatus(models.Model):
type = models.CharField(max_length=60)
def __unicode__(self):
return '%s' % self.type
class Adv(models.Model):
status = models.ForeignKey(ChoiceStatus)
#OR this method
STATUS = (
(1, u'new'),
(2, u'old'),
)
class Adv(models.Model):
status = models.IntegerField(u'??????', choices=STATUS, default=1,)
Using choices is appropriate when the items are virtually static: they don't change or don't change often and don't need to "do" anything on their own.
Use ForeignKey when the "choices" for that field are dynamic (could change at any moment or at a whim) or you need to associate additional data with those "choices".
However, for your purposes, both "years" and "status" are good candidates for using choices. There's only ever a certain defined number of car "statuses": new, used, etc. Years wouldn't be appropriate as a model of its own, so using choices is a good idea there too. However, I'd change it to something like:
YEAR_CHOICES = [(y, y) for y in range(1990, datetime.now().year+2)]
Where "1990" is the year you want to start with. datetime.now().year gets you the current year, and since range is not end-inclusive (it returns up to but not the last number) and you seem to be dealing with model years here (which are 1 greater than the current year), you have to increment it by total of 2.
Why do u want to define a foreign key relation when choices does the job for you? I would go with the choices way

Sorting products after dateinterval and weight

What I want is to be able to get this weeks/this months/this years etc. hotest products. So I have a model named ProductStatistics that will log each hit and each purchase on a day-to-day basis. This is the models I have got to work with:
class Product(models.Model):
name = models.CharField(_("Name"), max_length=200)
slug = models.SlugField()
description = models.TextField(_("Description"))
picture = models.ImageField(upload_to=product_upload_path, blank=True)
category = models.ForeignKey(ProductCategory)
prices = models.ManyToManyField(Store, through='Pricing')
objects = ProductManager()
class Meta:
ordering = ('name', )
def __unicode__(self):
return self.name
class ProductStatistic(models.Model):
# There is only 1 `date` each day. `date` is
# set by datetime.today().date()
date = models.DateTimeField(default=datetime.now)
hits = models.PositiveIntegerField(default=0)
purchases = models.PositiveIntegerField(default=0)
product = models.ForeignKey(Product)
class Meta:
ordering = ('product', 'date', 'purchases', 'hits', )
def __unicode__(self):
return u'%s: %s - %s hits, %s purchases' % (self.product.name, str(self.date).split(' ')[0], self.hits, self.purchases)
How would you go about sorting the Products after say (hits+(purchases*2)) the latest week?
This structure isn't set in stone either, so if you would structure the models in any other way, please tell!
first idea:
in the view you could query for today's ProductStatistic, than loop over the the queryset and add a variable ranking to every object and add that object to a list. Then just sort after ranking and pass the list to ur template.
second idea:
create a filed ranking (hidden for admin) and write the solution of ur formula each time the object is saved to the database by using a pre_save-signal. Now you can do ProductStatistic.objects.filter(date=today()).order_by('ranking')
Both ideas have pros&cons, but I like second idea more
edit as response to the comment
Use Idea 2
Write a view, where you filter like this: ProductStatistic.objects.filter(product= aProductObject, date__gte=startdate, date__lte=enddate)
loop over the queryset and do somthing like aProductObject.ranking+= qs_obj.ranking
pass a sorted list of the queryset to the template
Basically a combination of both ideas
edit to your own answer
Your solution isn't far away from what I suggested — but in sql-space.
But another solution:
Make a Hit-Model:
class Hit(models.Model):
date = models.DateTimeFiles(auto_now=True)
product = models.ForeignKey(Product)
purchased= models.BooleanField(default=False)
session = models.CharField(max_length=40)
in your view for displaying a product you check, if there is a Hit-object with the session, and object. if not, you save it
Hit(product=product,
date=datetime.datetime.now(),
session=request.session.session_key).save()
in your purchase view you get the Hit-object and set purchased=True
Now in your templates/DB-Tools you can do real statistics.
Of course it can generate a lot of DB-Objects over the time, so you should think about a good deletion-strategy (like sum the data after 3 month into another model MonthlyHitArchive)
If you think, that displaying this statistics would generate to much DB-Traffic, you should consider using some caching.
I solved this the way I didn't want to solve it. I added week_rank, month_rank and overall_rank to Product and then I just added the following to my ProductStatistic model.
def calculate_rank(self, days_ago=7, overall=False):
if overall:
return self._default_manager.all().extra(
select = {'rank': 'SUM(hits + (clicks * 2))'}
).values()[0]['rank']
else:
return self._default_manager.filter(
date__gte = datetime.today()-timedelta(days_ago),
date__lte = datetime.today()
).extra(
select = {'rank': 'SUM(hits + (clicks * 2))'}
).values()[0]['rank']
def save(self, *args, **kwargs):
super(ProductStatistic, self).save(*args, **kwargs)
t = Product.objects.get(pk=self.product.id)
t.week_rank = self.calculate_rank()
t.month_rank = self.calculate_rank(30)
t.overall_rank = self.calculate_rank(overall=True)
t.save()
I'll leave it unsolved if there is a better solution.