Django - form_valid() vs save() - python

In django forms, for saving other data I usually use form_valid() but as I can also use save() method of formclass.
Today I overrided save() instead of form_valid() and I got problem with my manytomanyfield.
When using , the values of manytomanyfield are not saved but when I use form_valid() they start saving. Can anybody tell me the reason and what are the differences between both, which is the most convenient method and in what situation?
Here is my overriding of save() method:
class ProductCreateForm(forms.ModelForm):
sizes = make_ajax_field(ProductCreateModel,'sizes','sizes')
colours = make_ajax_field(ProductCreateModel,'colours','colours')
class Meta:
model = ProductCreateModel
fields = ('title','category',
'regions',)
def __init__(self,*args,**kwargs):
self.request = kwargs.pop("request")
super(ProductCreateForm, self).__init__(*args, **kwargs)
def save(self):
product = super(ProductCreateForm, self).save(commit=False)
user = self.request.user
product.location = user.user_location
product.save()
return product
When I override form_valid() method:
def get_form_kwargs(self):
kwargs = super(ProductCreateView,self).get_form_kwargs()
kwargs.update({'request':self.request})
return kwargs
def form_valid(self, form):
product = form.save(commit=False)
user = self.request.user
form.instance.user = user
form.instance.location = user.user_location
form.save()
return super(ProductCreateView, self).form_valid(form)
sizes,colours and regions are m2m fields, as I mentioned when I overrides save() values of m2m not get saved but when I overrides form_valid they start saving.

If you save a form with commit=False, you must call the form's save_m2m method to save the many-to-many data. See the docs for more info.
If you decide to use the form_valid method, I would change the following things:
update the instance returned by form.save() and save it, instead of calling form.save() again.
explicitly call form.save_m2m()
return a redirect response instead of calling super().form_valid() (which will save the form again)
Putting that together, you get:
def form_valid(self, form):
product = form.save(commit=False)
product.user = self.request.user
product.location.location = user.user_location
product.save()
form.save_m2m()
return redirect('/success-url/')

About your problem with manytomany i guess is the order they do things... Form > Admin > Models, when you use form_valid is the first thing they do before check other things in chain, while using save is the last, maybe can be because or other things too...
The best way is always use form_valid instead of raw save
form_valid first check the Clean function if there is any native validations errors or custom validations and only then save your models
save just save it without validate then with your form with your validations
Example
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject:
# Only do something if both fields are valid so far.
if "help" not in subject:
raise forms.ValidationError(
"Did not send for 'help' in the subject despite "
"CC'ing yourself."
)
Source: https://docs.djangoproject.com/en/2.0/ref/forms/validation/

Related

Django: save(commit=False) on formset CBV is triggering customized model save() actions

I'm using CBV CreateView to display a couple of pages with formsets to the user.
When the model behind a given formset/CreateView is a common one (it will became clearer later), everything works fine using the following logic on the view:
class Create(CreateView):
...
def form_valid(self, formset):
instances = formset.save(commit=False)
for instance in instances:
instance.user = self.request.user
instance.save()
return super(Create, self).form_valid(formset)
However, on one of the models, I had to add extra actions to the model save() method. Namely, I need to create child objects when the parents are saved. Something like:
class Parent(models.Model):
...
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.child_set.create(...., *args, **kwargs)
In this particular case, the child object is being created twice and I believe that the formset.save(commit=False) is the culprit.
I tried replacing the child_set.create() for
child = Child(...parameters, parent=self)
child.save(*args, **kwargs)
But it yields the same result. How can I prevent that?
The .form_valid(…) method [Django-doc] of a CreateView [Django-doc], will call .save() on the form, and this will thus invoke a new round of saving all objects.
You can set the .user of the instances, and then let the CreateView save these instances. This thus means that you implement this as:
class Create(CreateView):
# …
def form_valid(self, formset):
instances = formset.save(commit=False)
for instance in instances:
instance.user = self.request.user
# no instance.save()
# ↓ this will save the instances
return super().form_valid(formset)
That being said, it might be better to work with a .get_or_create(…) [Django-doc] over a .create(…) [Django-doc], since now you will create a Child object each time you save the Parent object, which is likely not the intended effect.

How to auto insert the current user when creating an object in django Create template?

I have a database of articles with a
User=models.ForeignKey(User,related_name="Company_Owner",
on_delete=models.CASCADE)
Where User is imported as follows:
from django.contrib.auth.models import User.
I would like to auto insert the current active user to the User field when a particular user submits the article.
I have done this in django admin:
class companyAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if getattr(obj, 'User', None) is None:
obj.User = request.user
obj.save()
But it is changing for the superusers only if I login to django admin,it doesnot change for all users if I submit a form from template it doesnot return the specific user.
Anyone have any suggestions?
You could override the get_changeform_initial_data of ModelAdmin class:
def get_changeform_initial_data(self, request):
initial = {
'user': request.user
}
return initial
You could also set other's fields initial values, if you'd like. This method is only called when you're adding an object. In its implementation, the default is to populate fields based on GET params:
django.contrib.admin.options.py
def get_changeform_initial_data(self, request):
"""
Get the initial form data.
Unless overridden, this populates from the GET params.
"""
initial = dict(request.GET.items())
for k in initial:
try:
f = self.model._meta.get_field(k)
except FieldDoesNotExist:
continue
# We have to special-case M2Ms as a list of comma-separated PKs.
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
return initial
Edit: you could use the same logic if you want to implement this in a normal view. In this case, you would instantiate the ModelForm like this:
form = ArticleForm(request.POST or None, initial={'user': request.user})
Actually there is nothing need to do . request.user representing the currently logged-in user. If the user isn’t currently logged in, user will be set to an instance of AnonymousUser.
just write the view where a particular user submits the article.
Got the Answer--
Done something like this in my view and it worked---
class group1CreateView(LoginRequiredMixin,CreateView):
form_class = group1Form
template_name = "accounting_double_entry/group1_form.html"
def form_valid(self, form):
form.instance.User = self.request.user
return super(group1CreateView, self).form_valid(form)
Thank you everyone for replying...It means a lot...

How to configure Django generic view for both GET and POST using model form

I want to create a view function + template that displays a simple form (derived from a user model) and also captures the form submission. How do I do this using generic views in Django?
My user model is:
class User(models.Model):
email = models.EmailField(unique=True)
name = models.CharField(max_length=100)
I only need to capture the email field in the form.
I think there must be a simple way to do this using generic forms, however I'm not sure which one to use nor how to do it. The only other ways I know how to do it are:
1) Create UserForm explicitly and a single view function separating POST and GET requests. E.g., :
def contact(request):
if request.method == 'GET':
# display UserForm
....
elif request.method == 'POST':
# process form submission
....
2) Create two views (with seperate URLs) - one using generic view to display form and another view to receive form submission e.g.,:
class contact(generic.DetailView):
# display form from User model
model = User
....
def submit(request):
# process form submission
....
So, my two questions are:
can and how should this be implemented using ONLY a generic view?
which generic view should be used?
First part of the answer: use a single view. If you use a function view (which is by far the simplest solution), the canonical form-handling edit view looks like:
def myview(request, instance_id, ...):
instance = get_object_or_404(pk=instance_id)
if request.method == "POST":
form = MyForm(request.POST, ..., instance=instance)
if form.is_valid():
# assuming a ModelForm
form.save()
return redirect(somewhere)
# invalid forms will be re-rendered with the error messages
else:
form = MyForm(instance=instance)
return render(request, "myapp/mytemplate.html", {"form": form})
For a create view, you just remove all the instance_xxx parts. Or you can use the same view for both create and update making the instance_id optional:
def myview(request, instance_id=None, ...):
if instance_id is not None:
instance = get_object_or_404(pk=instance_id)
else:
instance = None
if request.method == "POST":
form = MyForm(request.POST, ..., instance=instance)
if form.is_valid():
# assuming a ModelForm
form.save()
return redirect(somewhere)
# invalid forms will be re-rendered with the error messages
else:
form = MyForm(instance=instance)
return render(request, "myapp/mytemplate.html", {"form": form})
If you want a class-based generic view they are documented here. I personally don't think there's much to gain from generic class-based views (except eventually headaches when you try to grasp the execution flow scattered amongst half a dozen base classes and mixins) but YMMV.
update
if I want to do some processing on the data (including adding in extra fields) before saving an instance to the DB, where would I do this?
Preferably in the form itself unless you need some other data that you don't want to pass to the form. For all forms you can process data at the validation stage. With a ModelForm you can also override the save() method itself:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('whatever', 'something')
def save(self, commit=True):
""" Save user and create a pro account """
instance = super(MyModelForm, self).save(commit=False)
instance.something_else = 42
if commit:
instance.save()
return instance
CreateView can work perfectly according to your requirements, You only need to create a form of contact models and success_url where user will redirect after form submission. It'll save automatic user data into models
class ContactView(generic.CreateView):
form_class = ContactForm
template_name = 'contact/index.html'
success_url = '/homepage/' . #Mention URL here
This can also be done using only CreateView.Specify email in fields as you need only the email field in the form.You can also process submitted form in form_valid method.
class UserCreate(CreateView):
model = User
fields = ['email']
success_url = '/your_success_url/'
#transaction.atomic
def form_valid(self, form):
new_user = form.save(commit=False)
# process your submitted form here.
# eg. add any extra fields as:
# new_user.something = something
new_user.save()
return super().form_valid(form)

Set field value in Django Form clean() method, if this field not passed in constructor

I need set field value, not passed to Django Form constructor.
I have model and form like this:
class Message(models.Model):
created = models.DateTimeField()
text = models.CharField(max_length=200, blank=True, null=True)
active = models.BooleanField(default=False)
class MessageForm(forms.ModelForm):
class Meta:
model = Message
exclude = ('created', 'active')
def clean(self):
# check if user is blocked
if user.is_admin():
self.cleaned_data['active'] = True
return self.cleaned_data
Expected: if current user is admin - I need automatically set message as active. User should not pass this parameter by form.
Actual: I see that saved message always have flag "False" (I can delete condition and in this case I also see that message is not active).
Please help me understand, how can I do set this "active" flag in clean() method.
The previous answer would work, but I like encapsulating all the form's internal operations like what to show and what not, within the form. I know you mentioned you don't want to send a field value to the constructor, but if you don't mind sending the user, your solution would work.
i.e., your constructor:
def __init__(self, user):
self.user = user
super(BaseForm, self).__init__()
then in your clean, you just change the user to self.user.
There is another added benefit to this. Say tomorrow you want to assign more fields based on your user, you don't need to add anything to the views, you can simply add it to the form.
EDIT:
When you add a field to exclude, it is not available in the cleaned data. Instead, set its widget as hidden.
active = forms.BooleanField(widget=forms.HiddenInput)
EDIT 2: If you really don't want the field in the form
In this case, instead of overriding the clean, why don't you override the save?
def save (self):
super(BaseForm, self).save()
if user.is_admin():
self.instance.active=True
super(BaseForm, self).save()
Don't do this in the form's clean() method, do this in the view.
def your_view(request):
if request.method == 'POST':
form = MessageForm(data=request.POST)
if form.is_valid():
new_message = form.save(commit=False)
if user.is_admin():
new_message.active = True
However, if you also want to handle the case where your user is not admin using the same form, you can look at incorporating similar logic in the form's init() instead of the view, probably by passing info about the user from the view to the form's init()
Use this:
def message_form_factory(user):
class MessageForm(forms.ModelForm):
def clean(self):
# check if user is blocked
if user.is_admin():
self.cleaned_data['active'] = True
return self.cleaned_data
return MessageForm
And in your view use:
form = message_form_factory(request.user)()
form = message_form_factory(request.user)(request.POST)

Override save_model on Django InlineModelAdmin

I have a model that has a user field that needs to be auto-populated from the currently logged in user. I can get it working as specified here if the user field is in a standard ModalAdmin, but if the model I'm working with is in an InlineModelAdmin and being saved from the record of another model inside the Admin, it won't take.
Here's what I think is the best solution. Took me a while to find it... this answer gave me the clues: https://stackoverflow.com/a/24462173/2453104
On your admin.py:
class YourInline(admin.TabularInline):
model = YourInlineModel
formset = YourInlineFormset
def get_formset(self, request, obj=None, **kwargs):
formset = super(YourInline, self).get_formset(request, obj, **kwargs)
formset.request = request
return formset
On your forms.py:
class YourInlineFormset(forms.models.BaseInlineFormSet):
def save_new(self, form, commit=True):
obj = super(YourInlineFormset, self).save_new(form, commit=False)
# here you can add anything you need from the request
obj.user = self.request.user
if commit:
obj.save()
return obj
I know I'm late to the party, but here's my situation and what I came up with, which might be useful to someone else in the future.
I have 4 inline models that need the currently logged in user.
2 as a created_by type field. (set once on creation)
and the 2 others as a closed_by type field. (only set on condition)
I used the answer provided by rafadev and made it into a simple mixin which enables me to specify the user field name elsewhere.
The generic formset in forms.py
from django.forms.models import BaseInlineFormSet
class SetCurrentUserFormset(forms.models.BaseInlineFormSet):
"""
This assume you're setting the 'request' and 'user_field' properties
before using this formset.
"""
def save_new(self, form, commit=True):
"""
This is called when a new instance is being created.
"""
obj = super(SetCurrentUserFormset, self).save_new(form, commit=False)
setattr(obj, self.user_field, self.request.user)
if commit:
obj.save()
return obj
def save_existing(self, form, instance, commit=True):
"""
This is called when updating an instance.
"""
obj = super(SetCurrentUserFormset, self).save_existing(form, instance, commit=False)
setattr(obj, self.user_field, self.request.user)
if commit:
obj.save()
return obj
Mixin class in your admin.py
class SetCurrentUserFormsetMixin(object):
"""
Use a generic formset which populates the 'user_field' model field
with the currently logged in user.
"""
formset = SetCurrentUserFormset
user_field = "user" # default user field name, override this to fit your model
def get_formset(self, request, obj=None, **kwargs):
formset = super(SetCurrentUserFormsetMixin, self).get_formset(request, obj, **kwargs)
formset.request = request
formset.user_field = self.user_field
return formset
How to use it
class YourModelInline(SetCurrentUserFormsetMixin, admin.TabularInline):
model = YourModel
fields = ['description', 'closing_user', 'closing_date']
readonly_fields = ('closing_user', 'closing_date')
user_field = 'closing_user' # overriding only if necessary
Be careful...
...as this mixin code will set the currently logged in user everytime for every user. If you need the field to be populated only on creation or on specific update, you need to deal with this in your model save method. Here are some examples:
class UserOnlyOnCreationExampleModel(models.Model):
# your fields
created_by = # user field...
comment = ...
def save(self, *args, **kwargs):
if not self.id:
# on creation, let the user field populate
self.date = datetime.today().date()
super(UserOnlyOnCreationExampleModel, self).save(*args, **kwargs)
else:
# on update, remove the user field from the list
super(UserOnlyOnCreationExampleModel, self).save(update_fields=['comment',], *args, **kwargs)
Or if you only need the user if a particular field is set (like boolean field closed) :
def save(self, *args, **kwargs):
if self.closed and self.closing_date is None:
self.closing_date = datetime.today().date()
# let the closing_user field set
elif not self.closed :
self.closing_date = None
self.closing_user = None # unset it otherwise
super(YourOtherModel, self).save(*args, **kwargs) # Call the "real" save() method.
This code could probably be made way more generic as I'm fairly new to python but that's what will be in my project for now.
Only the save_model for the model you're editing is executed, instead you will need to use the post_save signal to update inlined data.
(Not really a duplicate, but essentially the same question is being answered in Do inline model forms emmit post_save signals? (django))
I had a similar issue with a user field I was trying to populate in an inline model. In my case, the parent model also had the user field defined so I overrode save on the child model as follows:
class inline_model:
parent = models.ForeignKey(parent_model)
modified_by = models.ForeignKey(User,editable=False)
def save(self,*args,**kwargs):
self.modified_by = self.parent.modified_by
super(inline_model,self).save(*args,**kwargs)
The user field was originally auto-populated on the parent model by overriding save_model in the ModelAdmin for the parent model and assigning
obj.modified_by = request.user
Keep in mind that if you also have a stand-alone admin page for the child model you will need some other mechanism to keep the parent and child modified_by fields in sync (e.g. you could override save_model on the child ModelAdmin and update/save the modified_by field on the parent before calling save on the child).
I haven't found a good way to handle this if the user is not in the parent model. I don't know how to retrieve request.user using signals (e.g. post_save), but maybe someone else can give more detail on this.
Does the other model save the user? In that case you could use the post_save signal to add that information to the set of the inlined model.
Have you tried implementing custom validation in the admin as it is described in the documentation? Overriding the clean_user() function on the model form might do the trick for you.
Another, more involved option comes to mind. You could override the admin template that renders the change form. Overriding the change form would allow you to build a custom template tag that passes the logged in user to a ModelForm. You could then write a custom init function on the model form that sets the User automatically. This answer provides a good example on how to do that, as does the link on b-list you reference in the question.

Categories