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

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

Related

Django Rest Framework - overriding save in backend is not creating custom id

I am working on a project. I have Django for my backend and Vue for my front end. When using the templates and saving in the Django project I have no issues.
However, when I POST to my projects API the following save from my modal is not being created.
models.py
class DevProjects(models.Model):
PROJECT_TYPE = [
('New Application', 'New Application'),
('Update Application', 'Update Application'),
('Bug Fixes', 'Bug Fixes')
]
PROJECT_STATUS = [
('New', 'New'),
('In Progress', 'In Progress'),
('Complete', 'Complete'),
]
project_id = models.CharField(max_length=15, editable=False, unique=True)
project_title = models.CharField(max_length=100)
project_desc = models.CharField(max_length=500)
project_category = models.CharField(max_length=25, choices=PROJECT_TYPE, null=True, blank=True)
project_status = models.CharField(max_length=25, choices=PROJECT_STATUS, default='New')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
created_by = models.ForeignKey(User, related_name='projects', on_delete=models.CASCADE)
def save(self, *args, **kwargs):
super(DevProjects, self).save(**kwargs)
self.project_id = 'PROJ-' + str(self.id)
super(DevProjects, self).save(**kwargs)
def __str__(self):
return self.project_title
I have the project_id being created on save which gets the original ID but just adds 'PROJ-' in front. Whenever I submit the form from my frontend, that save definition is not being called, thus not creating the project_id.
Project ID is what I use to send a GET request to get the projects.
serailizers.py
class DevProjectSerializer(serializers.ModelSerializer):
class Meta:
model = DevProjects
fields = ("project_id", "project_title", "project_desc", "project_category", "project_status")
views.py
class DevProjectViewSet(viewsets.ModelViewSet):
serializer_class = DevProjectSerializer
queryset = DevProjects.objects.all()
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
Whenever I post, I get the following error:
IntegrityError: UNIQUE constraint failed: ckcSupportWeb_devprojects.project_id
What do I need to do for the project_id to generate when POSTing from DRF? Any and all help is appreciated.
UPDATE
I can try to use the following code in my viewset:
def create(self, *args, **kwargs):
self.project_id = 'PROJ-' + str(self.id)
super(DevProjectViewSet, self).save(**kwargs)
But, I get the following error:
self.project_id = 'PROJ-' + str(self.id)
AttributeError: 'DevProjectViewSet' object has no attribute 'id'
I am truly stuck on how to handle this for API post requests.
From what I can understand, you can relax the unique constraint on the project_id and your previous code should work fine.
Since the project code is not editable, it won't be updated from a POST API call.
I was able to get rid of all of these issues with writing a simple function in utils.py that gets the latest ID created, adds 1 and then sets the new project_id.
Try with this code snippet
def save(self, *args, **kwargs)
self.project_id = 'PROJ-' + str(self.id)
super(DevProjects, self).save(**kwargs)

Update datetimefiled of all related models when model is updated

I have two models (Post and Display). Both have Datetime-auto fields. My problem is that i want to update all display objects related to a post, once a post is updated.
I have read here that you could override one models save method, but all the examples are About updating the model with the foreign key in it and then call the save method of the other model. In my case it's the other way arround. How can i do this ?
class Post(models.Model):
title = models.CharField(max_length=40)
content = models.TextField(max_length=300)
date_posted = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
rooms = models.ManyToManyField(Room, related_name='roomposts', through='Display')
def __str__(self):
return self.title
def get_absolute_url(self):
return "/post/{}/".format(self.pk)
class Display(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
room = models.ForeignKey(Room, on_delete=models.CASCADE)
isdisplayed = models.BooleanField(default=0)
date_posted = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.isdisplayed)
i want to update the date_posted of all related Display-objects once their related post is changed. I do not know if overriding the save-method works here.
in this case you should have a look at django's reverse foreign key documentation
https://docs.djangoproject.com/en/2.2/topics/db/queries/#following-relationships-backward
in your case you can override the save method on your Post model
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
#either: this excutes many sql statments
for display in self.display_set.all():
display.save()
#or faster: this excute only one sql statements,
#but note that this does not call Display.save
self.display_set.all().update(date_posted=self.date_posted)
The name display_set can be changed using the related_name option
in Display, you can change it:
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='displays')
Then, instead of using self.display_set in your code, you can use self.displays
Overriding save method works, but that's not were you should go, imo.
What you need is signals:
#receiver(post_save, sender=Post)
def update_displays_on_post_save(sender, instance, **kwargs):
if kwargs.get('created') is False: # This means you have updated the post
# do smth with instance.display_set
Usually it goes into signals.py.
Also you need to include this in you AppConfig
def ready(self):
from . import signals # noqa

automatically create a model object when an object of another table is created or updated in django models

I have two models in Django and I want to automatically create an object of History when an object of Food is created or updated and set the food_price attribute of History to the price attribute of the created Food object. My purpose is to have a history of food change price. How can I achieve that?
My models.py is:
class Food(models.Model):
food_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=30, null=False, blank=False)
desc = models.TextField(max_length=200)
price = models.IntegerField(null=False, blank=False)
f_thumbnail = models.ImageField(upload_to=get_food_t_image_name)
DDD_data = models.ImageField(upload_to=get_food_d_image_name)
availability = models.BooleanField(default=True)
discount = models.IntegerField(default=0)
def __str__(self):
return '%s %s %s' % (self.name, self.category_id, self.price)
class History(models.Model):
id = models.AutoField(primary_key=True)
food_id = models.IntegerField()
food_price = models.IntegerField()
history_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s %s %s' % (self.id, self.food_id.name, self.food_id.price)
Thanks in advance.
I think you have two ways.
First, use __save__() method in model.
In django model, (models.Model), there's __save__ method. When model object saves, you can do an additional feature that inherits this method.
For more information, please check official docs save()
Second, use signals
Django supports many signals including django.db.models.signals. It includes pre_save, post_save, and so on. So before(after) saving model object, you can do sth in signals.
Please check official docs signals
I think __save__() method is more fit to your purpose. So your code will be...
class Food(models.Model):
food_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=30, null=False, blank=False)
desc = models.TextField(max_length=200)
price = models.IntegerField(null=False, blank=False)
f_thumbnail = models.ImageField(upload_to=get_food_t_image_name)
DDD_data = models.ImageField(upload_to=get_food_d_image_name)
availability = models.BooleanField(default=True)
discount = models.IntegerField(default=0)
def __str__(self):
return '%s %s %s' % (self.name, self.category_id, self.price)
def save(self, force_insert=False, force_update=False, *args, **kwargs):
# you can add this for only existing model object
if self.pk:
# You can check if only 'price' field changed
_original_food = Food.objects.get(id=self.pk)
if _original_food.price != self.price:
# do somthing here like saving history...
History.objects.create(food_id=self.id, food_price=self.price)
super(Food, self).save(force_insert, force_update, *args, **kwargs)
super(Food, self).save(force_insert, force_update, *args, **kwargs)
This is just example. You can add & modify your code. I hope it helps you.

Django save() method not producing desired output

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

Adding custom field and updating model problem in django

I need to add a colorpicker to my django model and wrote a custom widget. However when I add this colordfield to my model, django gives this error:
column mediaplanner_ievent.color does not exist
LINE 1: ...nt"."bits", "mediaplanner_ievent"."capture_link", "mediaplan...
My model is :
from mediaplanner.custom_widgets import ColorPickerWidget
class ColorField(models.CharField):
def __init__(self,*args, **kwargs):
kwargs['max_length'] = 10
super(ColorField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
kwargs['widget'] = ColorPickerWidget
return super(ColorField, self).formfield(**kwargs)
class iEvent(models.Model):
name = models.CharField(verbose_name= u"Uygulama Adı", max_length=100, unique=True)
bits = models.CommaSeparatedIntegerField(verbose_name= u"Bitler",max_length=100)
capture_link = models.URLField(verbose_name= u"Capture URL", null=True, blank=True)
color = ColorField(blank=true)
class Meta:
verbose_name = u"red button"
verbose_name_plural = u"red buttonlar"
def __unicode__(self):
return smart_str( "%s"% self.name )
The strange thing is, when I looked my database, there exist colorfield. I don't want to delete the db and load it again. But ofcourse if it's the only solution, then no choice ..
So someone can help me how to solve it?
The field in your database is named colorfield bu the field in your model is named color. You have to change one or the other to make it work again.