Django using an object in an ORM query, within its __init__ function - python

I have a model which exists only to combine a number of other models. There are a series of 'payable' objects, with need to be clubbed together into a 'payment' object, called 'ClubbedPayment'.
The model is given a list of payable objects, and it uses them to calculate various attributes. This works fine, so far. These payable objects have a foreign key to ClubbedPayment. Rather than going through the payable objects and adding the new ClubbedPayment object to each of them as a foreign key, it seems easier to add them to ClubbedPayment.payable_set(). I've tried to do this, but it fails because in the __init__ function, the ClubbedPayment object is not yet saved:
class ClubbedPayment(models.Model):
recipient = models.ForeignKey(User, on_delete=models.PROTECT)
paid_at = models.DateTimeField(null=True)
is_unpaid = models.BooleanField(default=True)
total = models.DecimalField(max_digits=6, decimal_places=2)
payment_id = models.UUIDField(null=True, editable=False)
response = models.CharField(null=True, max_length=140)
payment_type = models.CharField(max_length=2, choices=PAYMENT_TYPES)
def __init__(self, *args, **kwargs):
super().__init__()
objects = kwargs["objects"]
recipients = set([x.recipient for x in objects])
types = set([type(x) for x in objects])
if len(recipients) > 1:
raise Exception("Cannot club payments to more than one recipient")
self.total = sum([x.total for x in objects])
self.recipient = list(recipients)[0]
for x in objects:
self.payable_set.add(x)
Now, I know that overriding __init__ with non optional arguments is considered dodgy, because it will prevent saving elsewhere, but it's not a problem, because there is only one place in the entire codebase where these objects are created or modified.
The question is, how do I get around this? Calling save() from within the __init__ function seems like a bad plan ;)

Related

DJANGO: How can I duplicate attribute from other class?

I want to duplicate an attribute from other class.
class PedidoCliente(Pedido):
total_pagado = models.DecimalField(blank=True, max_digits=10, decimal_places=2,default = 0,verbose_name="Pagado $") # default 0 para el error for += int
barril_encargado = models.DecimalField(blank=True, default=0, max_digits=10, decimal_places=2,verbose_name="Barr. entregados")
fecha_entrega = models.DateField(verbose_name="Fecha Entrega")
class DetallePedidoCliente(DetallePedido):
comments = models.CharField(max_length=300, verbose_name="Comentarios")
precio_venta = models.DecimalField(max_digits=16, decimal_places=2, default = 0)
pedido = models.ForeignKey(PedidoCliente,on_delete=models.CASCADE)
fecha_entrega = the same from PedidoCliente
I'm new at OPP so sorry if it's a silly question.
Thanks!
One should not look to mirror fields of another model. This simply leads to extra work. One needs to update this mirrored field each time it is updated in the other table, etc.
Also it is not very useful when we have already cemented the relationship between the two tables using a Foreign Key.
To access a related fields data is very easy in Django. Suppose we have an instance of DetallePedidoCliente named instance using this we can access the fecha_entrega of the related model by the foreign key as follows:
instance.pedido.fecha_entrega
If one just wants an easy way to refer to this field one may consider adding a property to the model:
class DetallePedidoCliente(DetallePedido):
comments = models.CharField(max_length=300, verbose_name="Comentarios")
precio_venta = models.DecimalField(max_digits=16, decimal_places=2, default = 0)
pedido = models.ForeignKey(PedidoCliente,on_delete=models.CASCADE)
#property
def fecha_entrega(self):
return self.pedido.fecha_entrega
Now we can simply access the field as:
instance.fecha_entrega

How to manipulate value of one Model Field from another Model?

I have two models
class Employee(models.Model):
name = models.CharField(max_length=20)
ID = models.IntegerField()
basic_salary = models.IntegerField()
total_leave = models.IntegerField(default = 14)
paid_leave = models.IntegerField(default = 0)
unpaid_leave = models.IntegerField(default = 0)
def __str__(self):
return self.name
class Leave_management(models.Model):
name = models.OnetoOneField(Employee,on_delete= models.CASCADE)
reason = models.CharField(max_length=50)
from = models.DateTimeField()
to = models.DateTimeField()
total_days = models.IntegerField()
def __str__(self):
return self.name
So,i want to minus 'total_days' of 'model-Leave_management' from 'total_leave' field of 'model-Employee'. And as per leaves taken i want to update 'paid_leave' and 'unpaid_leave' sections.
I can perform so if these two models would be one model(example below), But i dont know how to perform so in different models.
def save(self,*args,**kwargs):
if self.total_days<=self.total_leave:
self.total_leave -= self.total_days
self.unpaid_leave = 14 - self.total_leave
else:
self.total_days -= 14
self.paid_leaves = self.total_days
super(Model_name,self).save(*args,**kwargs)
`
Please be guiding me.
In fact your OneToOneField(..) to an Employee is not a name. At the database level it will store values that correspond to primary keys of an Employee, and in Django, name will be a lazy fetch to the corresponding Employee. Therefore I suggest to rename your function to (for example) employee.
Another problem is that you define it as a OneToOneField. That means that an Employee has one Leave_management. But based on the fields (reason, from, to, etc.), it looks like an Employee can have zero, one, or more Leave_managements. So that means it is a ForeignKey.
So our model looks like:
class Leave_management(models.Model):
employee = models.ForeignKey(Employee,on_delete= models.CASCADE)
reason = models.CharField(max_length=50)
from = models.DateTimeField()
to = models.DateTimeField()
total_days = models.IntegerField()
def __str__(self):
return self.employee.name
Like the __str__ function already suggests, we can obtain the name of the employee by querying self.employee, and we can then fetch its .name attribute.
But now the challenge is what to do when save a Leave_management object. In that case the number of total_leave and paid_leave should be updated.
We first have to figure out the total number of total_days that are stored in Leave_management objects related to an Employee, this is equal to:
(Leave_management.objects.filter(employee=some_employee)
.aggregate(totals=Sum('total_days'))['totals'] or 0
So we can then subtract this from 14, and store the (possibly) remaining days in paid_leave, like:
class Leave_management(models.Model):
# ...
def save(self, *args, **kwargs):
super(Leave_management, self).save(*args, **kwargs)
totals = (Leave_management.objects
.filter(employee=some_employee)
.aggregate(totals=Sum('total_days'))['totals'] or 0
employee = self.employee
unpaid = min(14, totals)
employee.total_leave = 14 - unpaid
employee.unpaid_leave = unpaid
employee.paid_leave = totals - unpaid
employee.save()
Note: typically we do not handle this by overriding the .save(..) function, but by using Django signals: triggers that can be implemented when certain objects are saved, etc. This especially should be used since the objects can be changed circumventing the .save(..) function, and sometimes such objects might get deleted as well. So the above is not a good design decision.
Even when we use signals, it is a good idea to frequently (for example once a day) recalculate the total leave, and update the corresponding Employee models.

How to return objects by popularity that was calculated with django-hitcount

I have itens in my app that I want to be returned by "popularity". This popularity meaning the number of views the item has.
I'm using django-hitcount to do this. I saw here how I could get the number of hits of each object. But I don't want to load all my Item objects to memory to accomplish what I want because it's an unnecessary overload.
I want to return the N most popular itens to be passed to the view and the access number of each item.
My Item model is as bellow
class Item(models.Model, HitCountMixin):
nome = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=255, null=True)
imagem = models.ImageField(upload_to='itens/item/', null=True, blank=True)
descricao = RichTextUploadingField(null=True, blank=True)
categoria = models.ForeignKey(Categoria)
hit_count_generic = GenericRelation(
HitCount, object_id_field='object_pk',
related_query_name='hit_count_generic_relation')
def __str__(self):
return '{}'.format(self.nome)
def get_absolute_url(self):
from django.urls import reverse
return reverse('itens:detail_item', args=[str(self.slug)])
At first, in my View I was trying to get the most popular itens with this function
def get_most_popular_itens(amount):
return Item.objects.order_by('-hit_count.hits')[:amount]
But it didn't work. I couldn't understand how this contenttype/generic relationship works.
So, I saw how the database tables were and managed to do something functional (see bellow).
But it has one problem. The queryset returned isn't ordered by the number of views and I don't have access to this number.
Even more, it seems to me that my solution is at least bad.
So, I wanted any idea on how I could improve that, maybe taking some advantage from the Generic Relationship?
def get_most_popular_itens(amount):
ct = ContentType.objects.get_for_model(Item)
hit_counts = HitCount.objects.filter(content_type_id=ct.id).order_by('-hits')[:amount]
items = []
for hit in hit_counts:
items.append(hit.object_pk)
return Item.objects.filter(id__in=items)
This should work:
Item.objects.all().order_by('-hit_count_generic__hits')

Django: Linking two tables

first of all, I'm aware that this question might've been answered already, but there are two reasons why I'm opening another question: One, obviously, is I'm struggling with the Django syntax. Secondly, and perhaps more importantly, I'm not quite sure whether my database setup makes even sense at this point. So, please bear with me.
I work in a hospital and one of my daily stuggles is that, oftentimes, one single drug can have a lot of different names. So, I thought that'd be a good task to practice some Django with.
Basically I want two databases: One that simply links the drugs "nick name" to it's actual name. And another one which links the actual name to some additional information, something along the lines of a wiki page.
What I've come up with so far:
(django)django#ip:~/medwiki$ cat medsearch/models.py
from django.db import models
# Create your models here.
class medsearch(models.Model):
proprietary_name = models.CharField(max_length = 100, unique = True)
non_proprietary_name = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.non_proprietary_name
class medwiki(models.Model):
proprietary_name = models.ForeignKey('medisearch', on_delete=models.CASCADE)
cetegory = models.CharField(max_length = 255)
#wiki = models.TextField() etc.
def __str__(self):
return self.proprietary_name
(django)django#ip-:~/medwiki$
So, I can add a new "medsearch object" just fine. However, when adding the "Category" at medwiki I get __str__ returned non-string (type medsearch). Presumably, because there's more than one key in medsearch? I thus suspect that "FroeignKey" is not suited for this application and I know that there are other ways to link databases in Django. However, I don't know which one to choose and how to implement it correctly.
Hopefully, some of you have some ideas?
EDIT: Here's what I've come up with so far:
class Proprietary_name(models.Model):
proprietary_name = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.proprietary_name
class Category(models.Model):
category = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.category
class Mediwiki(models.Model):
proprietary_name = models.ManyToManyField(Proprietary_name)
non_proprietary_name = models.CharField(max_length = 100, unique = True)
category = models.ManyToManyField(Category)
wiki_page = models.TextField()
def __str__(self):
return self.non_proprietary_name
Now I can attribute different categorys and different proprietary_names to one drug. Which works great so far.
So does looking up the non-proprietary_name when I know the proprietary "nick name".
>>> Mediwiki.objects.get(proprietary_name__proprietary_name="Aspirin")
<Mediwiki: acetylsalicylic acid>
>>>
However, I'd also like to display all the proprietary_names, when I know the non_proprietary_name. Do I have to further change the database design, or am I just missing some other thing here?
This would work:
return self.proprietary_name.proprietary_name
But that doesn't really make sense !
The main issue is that you've called the foreign key to medsearch, proprietary_name.
The second issue is just a convention one. In Python (and many programming languages), classes must start with an uppercase letter.
The following would be better:
class MedSearch(models.Model):
proprietary_name = models.CharField(max_length=100, unique=True)
non_proprietary_name = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.non_proprietary_name
class MedWiki(models.Model):
med_search = models.ForeignKey('MedSearch', on_delete=models.CASCADE, related_name='wikis')
cetegory = models.CharField(max_length = 255)
#wiki = models.TextField() etc.
def __str__(self):
return self.med_serach.proprietary_name
As you note, the proprietary_name field on medwiki is a ForeignKey. You can't return that value directly from the __str__ method because that needs to return a string. You need to convert that value into a string before returning it: either use the default string representation of the medsearch instance:
return str(self.proprietary_name)
or choose a specific string field to return:
return self.proprietary_name.proprietary_name

Django. Doing complex calculations in database

Here's part of my model file :
class Metric(models.Model):
Team = models.ForeignKey(Team)
metric_name = models.CharField(max_length = 40)
def __unicode__(self):
return self.metric_name
class Members(models.Model):
Metric = models.ForeignKey(Metric, through="Calculate")
member_name = models.CharField(max_length = 40, null=True, blank=True)
week_one = models.IntegerField(null=True, blank=True)
week_two = models.IntegerField(null=True, blank=True)
week_three = models.IntegerField(null=True, blank=True)
week_four = models.IntegerField(null=True, blank=True)
total = models.IntegerField(null=True, blank=True)
def __unicode__(self):
return self.member_ID
def save(self, *args, **kwargs):
self.total = int(self.week_one)+int(self.week_two)+int(self.week_three)+int(self.week_four)
super(Members, self).save(*args, **kwargs) # Call the "real" save() method.
Now what I want to do is. I want to calculate the number of members per metric the aggregate total of all the members in a metric and the highest total among all the members in a metric.
I can't figure out a way to do this in Django.
I want to make these calculations and store them in the database.
Can anyone please help me out with this.
Thanks
If you wish to "memoize" these results, there are at least two paths that you could follow:
A per-row post-save trigger on the Members table that updates "members_count", "members_total" and "members_max" fields in the Metric table.
The challenge with this is in maintaining the trigger creation DDL alongside the rest of your code and applying it automatically whenever the models are re-created or altered.
The Django ORM does not make this especially easy. The commonest migration tool ( south ) also doesn't go out of its way to make this easy. Also note that this solution will be specific to one RDBMS and that some RDBMSs may not support this.
You could create "synthetic" fields in your Metric model then use a post-save signal handler to update them whenever a Member is added or changed.
class Metric(models.Model):
Team = models.ForeignKey(Team)
metric_name = models.CharField(max_length = 40)
members_count = models.IntegerField()
members_max = models.IntegerField()
members_total = models.IntegerField()
def __unicode__(self):
return self.metric_name
# signal handling
from django.db.models import signals
from django.dispatch import dispatcher
def members_post_save(sender, instance, signal, *args, **kwargs):
# Update the count, max, total fields
met = sender.Metric # sender is an instance of Members
metric.members_count = Members.objects.filter(Metric=met).count()
# more code here to do the average etc;
dispatcher.connect(members_post_save, signal=signals.post_save, sender=Members)
The django signals documentation here can be of use.
Caveats
While this sort of approach could be made to achieve your stated goal, it is brittle. You need to have test coverage that ensures that this signal handler always fires, even after you've done some refactoring of your code.
I would also consider using "related objects" queries [ documented at https://docs.djangoproject.com/en/dev/topics/db/queries/#related-objects ] eg, assuming we have a "me" instance of Metric
>> members_count = me.members_set.count()
>> # aggregation queries for the total and max
If these aggregates are not used very often, path #2 could be a viable and more maintainable option.

Categories