Creating/saving database objects in django ModelForm save method - django

I have model form JobPosting with a custom field location_query which I use to generate data for a model field location. In doing this I need to create an Address object for the location field and save it to the database. I believe the correct time to save this object is within an overloaded save method of JobPosting.
While the new Address object is created and saved, it does not get saved as the value of the JobPosting's location field, and I'm not sure why.
Below is a simplified example:
class Address(Model):
pass
class JobPosting(Model):
location = ForeignKey(Address, blank=True, null=True)
class JobPostingForm(forms.ModelForm):
location_query = forms.CharField(max_length=256)
class Meta:
model = JobPosting
fields = (
'location_query',
'location', # hidden field
}
def clean_location(self):
data = self.data.get('location_query')
addr = Address()
# do some stuff here to dump data into addr
return addr
def save(self, commit=True, *args, **kwargs):
if self.instance.location and not self.instance.location.uuid :
self.instance.location.save()
instance = super(JobPostingForm, self).save(commit=commit, *args, **kwargs)
return instance
The obnoxious thing is that the code above results in the JobPosting being saved with location as None, but if I save the address in the clean_location function it works correctly. Obviously I don't want to save a database object in a clean function, but I'm pulling my hair out trying to figure out why this is.

The solution is to set the location_id manually. I had been assuming since I'd seen it happen in all other cases, that the <field>_id member was automatically filled in on save, that it would happen here. I'm not sure why it isn't but adding instance.location_id = instance.location.uuid after saving the location did the trick.
I also moved the instance.location setting code to after the super save (as suggested by #SÅ‚awek) as self.instance will not always exist if it hasn't been passed in at form creation time.
The new code looks something like this:
def save(self, commit=True, *args, **kwargs):
instance = super(JobPostingForm, self).save(commit=commit, *args, **kwargs)
if instance.location and not instance.location.uuid :
instance.location.save()
instance.location_id = instance.location.uuid
instance.save()
return instance

Related

Django how to update a record rather than raise error on duplicate id

I guess what I need to do it overwrite my model's save method but do correct me if I'm wrong/have better suggestion.
This is what my model looks like:
class MergingModel(models.Model):
some_field = models.TextField()
And this is the unit test that I want to pass:
class MergingModelTests(TestCase):
def test_duplicates_are_overwriten(self):
MergingModel.create(id=1)
MergingModel.create(id=1, some_field="abc")
self.assertEquals(MergingModel.objects.count(),1)
self.assetEquals(MergingModel.objects.get(id=1).some_field,"abc")
I tried overwriting save method to check if record with id=x exists but that raised recursion errors, my code for save method looked like:
def save(self, *args, **kwargs):
if MergingModel.objects.filter(id__exact=self.id):
original = MergingModel.objects.get(id=self.id)
original.some_field = self.some_field
original.save()
else:
super().save( *args, **kwargs)
Then I tried overwriting create but I get error:
Key (id)=(ID1) already exists
So I'm not really sure what to do anymore.

Is it possible to do a post save when a specific field on a model changes?

If it is possible to do a post save based on a field change, would the code look something like this? Note: I want a Car object to get created each time the address of the House model changes.
def create_car(sender, **kwargs):
if kwargs['??????']:
car = Car.objects.filter(user=kwargs['instance'].user)
post_save.connect(create_car, sender=House.address)
I'm not sure what would go in the kwargs.
Thanks!
You can compare address fields in pre_save signal
#receiver(signals.pre_save, sender=House)
def create_car(sender, instance, **kwargs):
try:
old_instance = sender.objects.get(pk=instance.pk)
except sender.DoesNotExist:
return
if instance.address != old_instance.address:
car = Car.objects.filter(user=kwargs['instance'].user)
# todo

django: ModelForm access instance of new object

I wanted to ask if there is a way to access instance id in ModelForm save method. (Need an object in order to add some extra data).
def save(self, *args, **kwargs):
instance = super(MyForm, self).save(*args, **kwargs)
print instance
return instance
And in all cases I am getting instance before it's saved in database (so it does not have an id and I can't attach objects to it)
It isn't necessary to override the ModelForm save() function. Instead, it's better to call save with commit=False. The Django docs explain this in depth, but here's a quick example:
new_object = form.save(commit=False)
new_object.name = 'Whatever'
new_object.save()
By calling save with commit=False, you get an object back. You can do whatever you want with this object, but make sure to save it once you make your changes!

Django - Check diference between old and new value when overriding save method

thanks for your time.
I'm on Django 1.4, and I have the following code: Its the overriden save method for my Quest model.
#commit_on_success
def save(self, *args, **kwargs):
from ib.quest.models.quest_status_update import QuestStatusUpdate
created = not self.pk
if not created:
quest = Quest.objects.get(pk=self)
# CHECK FOR SOME OLD VALUE
super(Quest, self).save(*args, **kwargs)
I couldn't find out a smart way of doing this. It seems very silly to me to have to make a new query for the object i'm currently updating in order to find out an old instance value.
Is there a better way to do this?
Thank you all.
Francisco
You can store the old value inside the init method:
def __init__(self, *args, **kwargs):
super(MyModel, self).__init__(*args, **kwargs)
self.old_my_field = self.my_field
def save(self, *args, **kwargs):
print self.old_my_field
print self.my_field
You can probably use deepcopy or something alike to copy the whole object for later use in the save and delete methods.
Django doesn't cache the old values of the model instance, so you need to do that yourself or perform another query before save.
One common pattern is to use a pre-save signal (or put this code directly in your save() method, as you've done):
old_instance = MyModel.objects.get(pk=instance.pk)
# compare instance with old_instance, and maybe decide whether to continue
If you want to keep a cache of the old values, then you would probably do that in your view code:
from copy import deepcopy
object = MyModel.objects.get(pk=some_value)
cache = deepcopy(object)
# Do something with object, and then compare with cache before saving
There was a recent discussion on django-developers about this as well, with some other possible solutions.
I am checking the difference to old values using a django-reversion signal, but the same logic would apply to the save signals. The difference for me being that I want to save whether the field was saved or not.
#receiver(reversion.pre_revision_commit)
def it_worked(sender, **kwargs):
currentVersion = kwargs.pop('versions')[0].field_dict
fieldList = currentVersion.keys()
fieldList.remove('id')
commentDict = {}
print fieldList
try:
pastVersion = reversion.get_for_object(kwargs.pop('instances')[0])[0].field_dict
except IndexError:
for field in fieldList:
commentDict[field] = "Created"
comment = commentDict
except TypeError:
for field in fieldList:
commentDict[field] = "Deleted"
comment = commentDict
else:
for field in fieldList:
try:
pastTest = pastVersion[field]
except KeyError:
commentDict[field] = "Created"
else:
if currentVersion[field] != pastTest:
commentDict[field] = "Changed"
else:
commentDict[field] = "Unchanged"
comment = commentDict
revision = kwargs.pop('revision')
revision.comment = comment
revision.save()
kwargs['revision'] = revision
sender.save_revision

Django Admin: Detect if a subset of an object fields has changed and which of them

I need to detect when some of the fields of certain model have changed in the admin, to later send notifications depending on which fields changed and previous/current values of those fields.
I tried using a ModelForm and overriding the save() method, but the form's self.cleaned_data and seld.instance already have the new values of the fields.
Modifying the answer above... taking the brilliant function from Dominik Szopa and changing it will solve your relationship change detection: Use this:
def get_changes_between_models(model1, model2, excludes = []):
changes = {}
for field in model1._meta.fields:
if not (field.name in excludes):
if field.value_from_object(model1) != field.value_from_object(model2):
changes[field.verbose_name] = (field.value_from_object(model1),
field.value_from_object(model2))
return changes
Then in your code you can say (avoid try/except for performance reasons):
if (self.id):
old = MyModel.Objects.get(pk=self.id)
changes = get_changes_between_models(self, old)
if (changes):
# Process based on what is changed.
If you are doing this at the "model" level, there is no way to save the extra query. The data has already been changed by the time you reach the "Save" point. My first post, so forgive me if I sound like an idiot.
To avoid extra DB lookup, I modified constructor to remember initial value and use this in save method later:
class Package(models.Model):
feedback = models.IntegerField(default = 0, choices = FEEDBACK_CHOICES)
feedback_time = models.DateTimeField(null = True)
def __init__(self, *args, **kw):
super(Package, self).__init__(*args, **kw)
self._old_feedback = self.feedback
def save(self, force_insert=False, force_update=False, *args, **kwargs):
if not force_insert and self.feedback != self._old_feedback:
self.feedback_time = datetime.utcnow()
return super(Package, self).save(force_insert, force_update, *args, **kwargs)
In order to get differences of two model instances, you can also use this function. It compare to model instances and returns dictionary of changes.
What you'll need to do is get an extra copy of the object you're working on from the database inside the save method before fully saving it. Example:
class MyModel(models.Model):
field1 = models.CharField(max_length=50)
def save(self):
if self.id:
try:
old = MyModel.objects.get(pk=self.id)
if old.field1 != self.field1:
# Process somehow
except MyModel.DoesNotExist:
pass
super(MyModel, self).save()