django: ModelForm access instance of new object - django

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!

Related

Creating/saving database objects in django ModelForm save method

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

Modify object before save and passing it to serializer

I'm trying to set some fields before saving an object that a user wants to insert. For example, if a user wants to create a new instance, before saving it, I want to set the field owner equal to request.user and then call the create method from the parent. I've achieved this with the following code:
class ClassView(viewsets.ModelViewSet):
queryset = ModelClass.objects.all()
serializer_class = ModelClassSerializer
def create(self, request, pk = None):
if ModelClass.objects.filter(pk = request.user.id):
return Response({'detail' : "This user is already inserted" }, status = status.HTTP_401_UNAUTHORIZED)
return super(ClassView, self).create(request, pk = None)
def pre_save(self, obj):
obj.user_id = ModelClass.objects.get(pk = self.request.user.id)
It could be also that I want to set an attribute of the model according to some calculation with values coming from the POST request (those values are established as fields in the serializer).
Is the pre_save solution the correct way to go or am I missing something?
Thanks in advance.
I would say this is the correct way to go but if you simply want to set the object's user to the current request user, instead of:
obj.user_id = ModelClass.objects.get(pk = self.request.user.id)
...just use:
obj.user = self.request.user
The rest framework pre_save hook is there for your exact requirement but there exists other ones you may find useful. See http://www.django-rest-framework.org/api-guide/generic-views#genericapiview - under Save / deletion hooks.
However, if you require this data to be saved on the object instance outside of the rest framework (i.e. additionally within a normal Django view) you will most probably want to use the Django pre_save signal and hook your model up to it. That way the request user will be stored each time the object is saved, not just via the rest framework: https://docs.djangoproject.com/en/dev/ref/signals/

super() save method on Django auth user's user change form

I am trying to edit django.contrib.auth.forms.UserChangeForm. Basically, auth_user's user edit page.
https://github.com/django/django/blob/master/django/contrib/auth/forms.py
According to source code, the form does not have a save() method, so it should inherit from forms.ModelForm right?
For full code, see here
class MyUserAdminForm(forms.ModelForm):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(MyUserAdminForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id: # username and user id
... the rest of the __init__ is setting readonly fields
.... some clean methods .....
def save(self, *args, **kwargs):
kwargs['commit'] = True
user = super(MyUserAdminForm, self).save(*args, **kwargs)
print user.username
print 'done'
return user
When I hit save, it said 'UserForm' object has no attribute 'save_m2m'. I've googled quite a bit, and tried to use add() but didn't work. What's causing this behaviour?
The thing is: the two print statements are printed. But the value never saved into database. I thought that the 2nd line would have saved once already.
Thanks
Remove the kwargs['commit'] = True line and see what happen.
Django Admin would invoke form.save_m2m(), which is hooked to the form when commit is False, here. The unconditional overriding of kwargs['commit'] = True would break the setattr of save_m2m() to form thus no attribute error is raised. The actual affected logic is here:
def save_form(self, request, form, change):
"""
Given a ModelForm return an unsaved instance. ``change`` is True if
the object is being changed, and False if it's being added.
"""
return form.save(commit=False)
You could find out that your version of form.save() overriding commit=False to commit=True unconditionally, thus Django Admin fails to continue as it believes form.save(commit=False) is invoked and thus form.save_m2m() needs to be called.
Refs the doc:
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn't possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you've manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.

Django: How to override form.save()?

My model has quite a few boolean fields. I've broken these up into 3 sets which I'm rendering as a MultipleChoiceField w/ a modified CheckboxSelectMultiple.
Now I need to save this data back to the DB. i.e., I need to split the data returned by a single widget into multiple boolean columns. I think this is appropriate for the save() method, no?
Question is, how do I do I do it? Something like this?
def save(self, commit=True):
# code here
return super(MyForm, self).save(commit)
If so... how do I set the values?
self.fields['my_field'].value = 'my_flag' in self.cleaned_data['multi_choice']
Or something? Where's all the data stored?
The place you want your data to be stored is your new model instance:
def save(self, commit=True):
instance = super(MyForm, self).save(commit=False)
instance.flag1 = 'flag1' in self.cleaned_data['multi_choice'] # etc
if commit:
instance.save()
return instance

In a django model custom save() method, how should you identify a new object?

I want to trigger a special action in the save() method of a Django Model object when I'm saving a new record (not updating an existing record.)
Is the check for (self.id != None) necessary and sufficient to guarantee the self record is new and not being updated? Any special cases this might overlook?
Alternative way to checking self.pk we can check self._state of the model
self._state.adding is True creating
self._state.adding is False updating
I got it from this page
Updated: With the clarification that self._state is not a private instance variable, but named that way to avoid conflicts, checking self._state.adding is now the preferable way to check.
self.pk is None:
returns True within a new Model object, unless the object has a UUIDField as its primary_key.
The corner case you might have to worry about is whether there are uniqueness constraints on fields other than the id (e.g., secondary unique indexes on other fields). In that case, you could still have a new record in hand, but be unable to save it.
Checking self.id assumes that id is the primary key for the model. A more generic way would be to use the pk shortcut.
is_new = self.pk is None
The check for self.pk == None is not sufficient to determine if the object is going to be inserted or updated in the database.
The Django O/RM features an especially nasty hack which is basically to check if there is something at the PK position and if so do an UPDATE, otherwise do an INSERT (this gets optimised to an INSERT if the PK is None).
The reason why it has to do this is because you are allowed to set the PK when an object is created. Although not common where you have a sequence column for the primary key, this doesn't hold for other types of primary key field.
If you really want to know you have to do what the O/RM does and look in the database.
Of course you have a specific case in your code and for that it is quite likely that self.pk == None tells you all you need to know, but it is not a general solution.
You could just connect to post_save signal which sends a "created" kwargs, if true, your object has been inserted.
http://docs.djangoproject.com/en/stable/ref/signals/#post-save
Check for self.id and the force_insert flag.
if not self.pk or kwargs.get('force_insert', False):
self.created = True
# call save method.
super(self.__class__, self).save(*args, **kwargs)
#Do all your post save actions in the if block.
if getattr(self, 'created', False):
# So something
# Do something else
This is handy because your newly created object(self) has it pk value
I'm very late to this conversation, but I ran into a problem with the self.pk being populated when it has a default value associated with it.
The way I got around this is adding a date_created field to the model
date_created = models.DateTimeField(auto_now_add=True)
From here you can go
created = self.date_created is None
For a solution that also works even when you have a UUIDField as a primary key (which as others have noted isn't None if you just override save), you can plug into Django's post_save signal. Add this to your models.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
if created:
# do extra work on your instance, e.g.
# instance.generate_avatar()
# instance.send_email_notification()
pass
This callback will block the save method, so you can do things like trigger notifications or update the model further before your response is sent back over the wire, whether you're using forms or the Django REST framework for AJAX calls. Of course, use responsibly and offload heavy tasks to a job queue instead of keeping your users waiting :)
rather use pk instead of id:
if not self.pk:
do_something()
It is the common way to do so.
the id will be given while saved first time to the db
> def save_model(self, request, obj, form, change):
> if form.instance._state.adding:
> form.instance.author = request.user
> super().save_model(request, obj, form, change)
> else:
> obj.updated_by = request.user.username
>
> super().save_model(request, obj, form, change)
Would this work for all the above scenarios?
if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
In python 3 and django 3 this is what's working in my project:
def save_model(self, request, obj, form, change):
if not change:
#put your code here when adding a new object.
To know whether you are updating or inserting the object (data), use self.instance.fieldname in your form. Define a clean function in your form and check whether the current value entry is same as the previous, if not then you are updating it.
self.instance and self.instance.fieldname compare with the new value