Django save() method not producing desired output - django

I am working on patient databases. Two related class, one which stores details of patients, and the other which is just to track total numbers of different types of patients. The tracker class is as follow :
class Case (models.Model):
id = models.AutoField(primary_key=True)
time_stamp = models.DateTimeField(null=True, auto_now_add=True)
is_dicom = models.BooleanField(null=False, blank=True)
PatientId = models.IntegerField(null=True, blank=True, db_column="patientid")
def __unicode__(self):
return "%s" % self.id
This has a 'PatientId' field which refers to two different classes (hence not a foreign key to any one class) and just stores the pk of the referring class as integer. The referring classesa are Patient and PatientJpeg which run similar code on save() :
class Patient(models.Model):
id = models.AutoField(primary_key=True, db_column='pk')
case_id = models.OneToOneField(Case, null=True, blank=True, db_column='case_id')
pat_birthdate = models.CharField(max_length=160, blank=True)
pat_sex = models.CharField(max_length=160, blank=True)
created_time = models.DateTimeField(null=True, auto_now_add=True)
updated_time = models.DateTimeField(null=True, auto_now_add=True)
class Meta:
db_table = 'patient'
def __unicode__(self):
return u"%s" % self.id
def save(self):
if not(self.case_id):
x = 1
while (x < Case.objects.count() + 1):
if not Case.objects.filter(pk=x).exists():
break
else:
x += 1
newCase = Case(x)
newCase.is_dicom = True
newCase.PatientId = self.id
newCase.save()
self.case_id = newCase
super(Patient, self).save()
class PatientJpeg (models.Model):
id = models.AutoField(primary_key=True, db_column='pk')
case_id = models.OneToOneField(Case, null=True, blank=True, db_column='case_id')
pat_age = models.CharField(max_length=160, null=True, blank=True)
pat_sex = models.CharField(max_length=160, null=True, blank=True)
def __unicode__(self):
return u"%s" % self.id
def jpeg_paths(self):
array = []
for x in self.jpeg_set.all():
array.append(x.image.path)
return array
class Meta:
db_table = 'patient_jpeg'
def save(self):
if not(self.case_id):
x = 1
while (x < Case.objects.count() + 1):
if not Case.objects.filter(pk=x).exists():
break
else:
x += 1
newCase = Case(x)
newCase.is_dicom = False
newCase.PatientId = self.id
newCase.save()
self.case_id = newCase
super(PatientJpeg, self).save()
The problem is that when I save Patient or PatientJpeg, PatientId field (integer field in Case class) remains null. I have replicated this code in shell and it behaves properly, I dont know why it doesnt behave inside django.
Thanks for looking at the code

This code is, to be polite, pretty horrible. It's doing an insane amount of database access each time you create a new Patient - imagine you had 1000 Cases, just saving a new Patient would result in over 1000 database calls.
What's strange is that I can't understand why you need it. The case ID is an arbitrary number, so there doesn't seem to be any particular reason why you need to iterate through existing case IDs to find a blank one. This is especially so given that the ID is an AutoField, so will be allocated by the database anyway. Just get rid of all that and simply create a new case.
The other problem of course is that self.id does not exist when you first save the Patient. You need to ensure that it is saved before allocating the Case.
def save(self, *args, **kwargs):
if not(self.case_id):
if not self.id:
super(Patient, self).save(*args, **kwargs)
newCase = Case()
newCase.is_dicom = True
newCase.PatientId = self.id
newCase.save()
self.case_id = newCase
super(Patient, self).save(*args, **kwargs)
Other pointers: don't call your one-to-one relationship case_id: it's a case object, not an ID, so just call it case. Also, you should really create an abstract class as the parent of both Patient and PatientJpeg, and put the custom save logic there. Finally, your jpeg_paths method could be replaced by a list comprehension.

The time you call newCase.save(), your PatientJpeg record has not been saved to the database. AutoFields in django are evaluated when you try to save them to database, because id must be genertated by the databse. So when you call newCase.save(), self.id in newCase.PatientId = self.id is None
You must save your data after you call super(PatientJpeg, self).save() so self.id will have a valid not None value
Also I suggest you to use ContentTypes Framework if your ForeignKey might be elated to more than one table.

Before creating Case you need to save Patient, so i think the save method should be like:
def save(self):
if not self.id:
super(PatientJpeg, self).save()
if not self.case_id:
x = 1
while (x < Case.objects.count() + 1):
if not Case.objects.filter(pk=x).exists():
break
else:
x += 1
newCase = Case(x)
newCase.is_dicom = False
newCase.PatientId = self.id
newCase.save()
self.case_id = newCase
super(PatientJpeg, self).save()
Look also at django post_save signal

Related

django.db.utils.OperationalError: no such table: store_product

When I run makemigrations error is occurred.
Here is models.py
def unique_order_id():
not_unique = True
while not_unique:
uo_id = random.randint(1000000000, 9999999999)
if not Order.objects.filter(order_id=uo_id):
not_unique = False
return uo_id
class Product(models.Model):
#product code, name, category, unit price, current stock
product_code = models.IntegerField(unique=True)
name = models.CharField(max_length=100)
category = models.CharField(max_length=100)
unit_price = models.FloatField()
current_stock = models.IntegerField()
def __str__(self):
return self.name
class Order(models.Model):
order_id = models.IntegerField(unique=True,
default=unique_order_id)
customer_name = models.CharField(max_length=100)
phone = models.CharField(max_length=14)
email = models.EmailField()
qr_code = models.ImageField(upload_to='qr_codes', blank=True)
def __str__(self):
return self.customer_name
#override ths save method for creating qrcode based on fields on this model.
def save(self, *args, **kwargs):
qr_info = "Invoice No : "+ str(self.order_id)+" Name : "+self.customer_name +" Phone : "+str(self.phone)+ " Email : "+ self.email
qrcode_img = qrcode.make(qr_info)
#canvas = Image.new('RGB', (290, 290), 'white')
canvas = Image.new('RGB', (qrcode_img.pixel_size, qrcode_img.pixel_size), 'white')
canvas.paste(qrcode_img)
fname = f'qr_code-{self.customer_name}.png'
buffer = BytesIO()
canvas.save(buffer,'PNG')
self.qr_code.save(fname, File(buffer), save=False)
canvas.close()
super().save(*args, **kwargs)
class OrderItem(models.Model):
qty = models.IntegerField(default=0)
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True)
#this will return total price of product(unit_price*quntity)
#property
def get_total(self):
total = self.product.unit_price * self.qty
return total
This is forms.py codes
class ItemSelectForm(forms.Form):
p_name = forms.ChoiceField(label='Select Product',choices=list ((obj.product_code,obj.name) for obj in Product.objects.all()))
qty = forms.IntegerField()
#function for checking product avaiability
def clean_qty(self):
data = self.cleaned_data['qty']
product_code = self.cleaned_data['p_name']
product = Product.objects.get(product_code = product_code)
if data > product.current_stock:
raise forms.ValidationError(('Insufficient Stock'), code='ins_stock')
# Always return a value to use as the new cleaned data, even if
# this method didn't change it.
return data
This code works fine on my environment, but when i run this on another environment and run makamigrations this error shows up. After searching online, my understanding is, the p_name field in the form is causing the error, the query is running even before creating the table.
p_name = forms.ChoiceField(label='Select Product',choices=list ((obj.product_code,obj.name) for obj in Product.objects.all()))
How can i get out of this situation and solve the error !
You are executing a query when the p_name field on the ItemSelectForm is initialised, this initialisation happens when your application is parsed/loaded/started. Executing queries at start-up of your application is bad practice and in this case preventing you from running migrations as the query you are running is for a model that has not been migrated.
Use a ModelChoiceField instead and pass it a queryset, this queryset will not be executed on start-up and should alleviate this issue
class ItemSelectForm(forms.Form):
product = forms.ModelChoiceField(Product.objects.all(), label='Select Product')
qty = forms.IntegerField()
#function for checking product availability
def clean_qty(self):
data = self.cleaned_data['qty']
product = self.cleaned_data['product']
if data > product.current_stock:
raise forms.ValidationError(('Insufficient Stock'), code='ins_stock')
# Always return a value to use as the new cleaned data, even if
# this method didn't change it.
return data

i want create constraint in django

I want to create a constraint of the form code = <min_weight> - <max_weight> <weight_unit>
how do i do that i just learned it
class Size(models.Model):
code = models.CharField(max_length=200, primary_key=True)
uni = [("GC", "Gram/Con"), ("KC", "Kg/Con")]
min_weight = models.IntegerField(blank=False, null=False)
max_weight = models.IntegerField(blank=False, null=False)
weight_uni = models.CharField(max_length=10, choices=uni, default="KC")
def __str__(self):
return self.code
Since code is 100% determined by values of other fields, there is no need to store it as separate field in db. Rather, make it a property, like this:
class Size(models.Model):
uni = [("GC", "Gram/Con"), ("KC", "Kg/Con")]
min_weight = models.IntegerField(blank=False, null=False)
max_weight = models.IntegerField(blank=False, null=False)
weight_uni = models.CharField(max_length=10, choices=uni, default="KC")
#property
def code(self):
return '%s - %s %s' % (self.min_weight, self.max_weight, self.weight_uni)
def __str__(self):
return self.code
You can override the save() method and configure the following:
def save(self, *args, **kwargs):
self.code = self.min_weight - self.max_weight*self.weight_unit
return super(Size, self).save(*args, **kwargs)
That way each time a save is called(it is called when a value is changed) it will reevaluate the "code" field and update it if necessary.
Here is an example how to use constraints but I don't think that you would be able to achieve the same result:
class Meta:
constraints = [
models.CheckConstraint(
name='SimpleConstaint',
check=(models.Q(sold_units__gte=0)&models.Q(sold_units__lte=100))
)
]

Action when a foreign key is being changed on django

The question in general is about finding the modification of a foreign key of a model and call some function of the related model.
Assume I have two model class:
class Discount(models.Model):
def use(self, sell_item):
if self.max_price:
self.max_price -= sell_item.net()
if self.max_count:
self.max_count -= sell_item.amount
self.save()
def deuse(self, sell_item):
if self.max_price:
self.max_price += sell_item.net()
if self.max_count:
self.max_count += sell_item.amount
self.save()
max_price = models.PositiveIntegerField(blank=True,
null=True)
max_count = models.PositiveIntegerField(blank=True,
null=True)
amount = models.PositiveIntegerField(blank=False,
null=False)
class SellItem(models.Model):
def net(self):
price = self.amount * self.price
if self.discount:
price -= self.discount.amount * price / 100
return price * (1 + self.tax / 100)
amount = models.PositiveIntegerField(balnk=False,
null=False)
price = models.PositiveIntegerField(blank=False,
null=False)
tax = models.PositiveIntegerFeidl(blank=False,
null=False)
discount = models.ForeignKey(Discount,
blank=True,
null=True)
Now I want to execute use function whenever a discount add to an item and deuse it whenever it is being removed from an item. I found a post about it and to do that I write below code for sell item:
def __init__(self, *args, **kwargs):
self.dirty = False
self.pre_states = []
self.new_states = []
super(SellItem, self).__init__(*args, **kwargs)
def __setattr__(self, name, value):
if name == 'discount':
if hasattr(self, name):
pre_discount = self.discount
if pre_discount != value:
self.dirty = True
if pre_discount:
self.pre_states = ['pre_discount']
self.pre_discount = pre_discount
if value:
self.new_states = ['discount']
object.__setattr__(self, name, value)
def save(self, *args, **kwargs):
super(SellItem, self).save(*args, **kwargs)
if self.dirty:
if 'pre_discount' in self.pre_states:
self.pre_discount.deuse(self)
if 'discount' in self.new_states:
self.discount.use(self)
But it is not enough, because basically django would not fetch a foreign key when a new class is constructed, it instead just fill the _id item for it and whenever you need that it would fetch it from database, if I check for modification of discount_id instead of discount based on the order of setting of member values I may miss the previous discount because I have just current and previous discount_id not discount.
I know that it could possible implement with checking all of cases but I think after all I depend on django implementation of the behavior of database fetching which could be changed further.
I think there must be a proper and easier solution for just knowing the modification of a foreign key, I know there is some packages for storing history of modification but they are too much for my simple request.

Django: object disappearing from model

I have a simple django model with an integer field status:
from django.db import models
class MyObject(models.Model):
dateDownloaded = models.DateField(null=True)
dateShared = models.DateTimeField(null=False)
sharedBy = models.CharField(max_length=100, null=False)
sharedTo = models.CharField(max_length=100, null=False)
token = models.CharField(max_length=200, null=False)
status = models.IntegerField(null=False, default=0)
def save(self, *args, **kwargs):
if not self.pk: # First time saved
self.status = 0
self.token = get_random_string(length=32)
super(MyObject, self).save(*args, **kwargs)
I can add objects to my model and I have a simple helper that counts the number of created objects. Now I also have a call that does the following:
def resetStatus(request):
myObjects = MyObject.objects.all()
for myObject in myObjects:
myObject.status = 0
myObject.save()
return HttpResponse("reset done")
Issue is, after calling this, from time to time all the objects from my database have disappeared. Maybe I have done something wrong with my objects in between but I have no idea what it could be. How can I go about debugging this ?

Django - How to get self.id when saving a new object?

I have a problem in one of my models. I'm uploading an image, and I want to store the id (pk in the database table) but I need to know at which point Django will have access to self.id.
models.py
class BicycleAdItemKind(MPTTModel):
def url(self, filename):
pdb.set_trace()
url = "MultimediaData/HelpAdImages/ItemKind/%s/%s" % (self.id, filename)
return url
def item_kind_image(self):
return '<img align="middle" src="/media/%s" height="60px" />' % self.image
item_kind_image.allow_tags = True
# Bicicleta completa, Componentes para bicicleta, Acessorios para ciclista
n_item_kind = models.CharField(max_length=50)
parent = TreeForeignKey('self', null=True,
blank=True, related_name='children')
description = models.TextField(null=True, blank=True)
image = models.ImageField(upload_to=url, null=True, blank=True)
date_inserted = models.DateTimeField(auto_now_add=True)
date_last_update = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.n_item_kind
class MPTTMeta:
order_insertion_by = ['n_item_kind']
The problem is in the url() method; I can only get self.id when updating an object, I don't get the self.id when creating a new object. How can I modify this model so that I get self.id when creating a new object?
With the current code, when I'm creating a new object I will end up with a url like:
MultimediaData/HelpAdImages/ItemKind/None/somefile.jpg
And I need to have something like:
MultimediaData/HelpAdImages/ItemKind/35/somefile.jpg
Any clues?
If it's a new object, you need to save it first and then access self.id, because
"There's no way to tell what the value of an ID will be before you call save(),
because that value is calculated by your database, not by Django."
Check django's document https://docs.djangoproject.com/en/dev/ref/models/instances/
You might need to save this file/instance twice:
You can use a post_save signal on the model that looks for the created flag, and re-saves the instance updating the url (and moving/renaming the file as necessary), since the instance will now have an ID. Make sure you only do this conditioned on created, though, otherwise you will continuously loop in saving: saving kicks off a post-save signal, which does a save, which kicks off a post-save signal...
See https://docs.djangoproject.com/en/dev/ref/signals/#post-save
There is actually a way to trick this out.
class Test(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=150)
def __str__(self):
return self.name
def update_model(self):
# You now have both access to self.id and self.name
test_id = Test.objects.get(name=self.name).id
print(test_id)
# Do some stuff, update your model...
Test.objects.filter(id=test_id).update(name='New Name')
def save(self, *args, **kwargs):
super(Test, self).save(*args, **kwargs)
self.update_model() # Call the function
Note: You need to have the models.AutoField(primary_key=True) attribute set, otherwise the database will be updated with a new id but Django will not recognize it.
models.AutoField(primary_key=True)
I understand this is old but for anyone that stumbles across this in the future, here's actually how you do it now in Django.
def url(instance, filename):
pdb.set_trace()
url = "MultimediaData/HelpAdImages/ItemKind/%s/%s" % (instance.id, filename)
return url
q = Order.objects.values_list('id', flat=True).order_by('-id')[:1]
if len(q):
self.number = str(self.id) if self.id else str(int(q.get()) + 1)
else:
self.number = 1