I have a Formset made up by a Form where I exclude a required field that I want to fill programatically instead of asking the user to fill it.
My expectation is that I can exclude it from my request.POST dictionary, and add it with the line below, and that the is_valid() method will both use the request.POST data, and the initial data added to the instance passed to the form, to validate and save it.
form_kwargs={"instance": MyModel(sale=5)}
# My view.py
formset = self.get_formset(
data=self.request.POST,
form_kwargs={"instance": MyModel(sale=5)}
)
# Error here, 'sale' is not set.
if formset.is_valid():
formset.save()
The get_formset() method returns an instance of the formset.
# My formset factory method
def get_formset(self, **kwargs):
MyFormSet = forms.modelformset_factory(MyModel, form=MyForm)
...
return MyFormSet(**kwargs)
No, Django will never use initial data in place of missing posted data - otherwise how could you ever use a form to set a field to empty? Instead, you should exclude that field from the form, in which case the existing instance value will be preserved.
Either do this explicitly in the Meta class of MyForm, or pass the exclude parameter to modelformset_factory.
Related
Using a generic CreateView in Django I'm trying to save only the fields that have been changed by user.
I'm trying to do this in my view:
def form_valid(self, form):
if form.has_changed():
for field in form:
if field.name in form.changed_data:
continue
else:
form.instance.field=None
In the last line I got this error:
object has no attribute 'field'
Is there a way to dynamically access to each field in the form? so I could change it?
Is there a way to dynamically access to each field in the form.
Yes.
form.fields[field_name]
So I could change it?
Yes.
form.fields[field_name] = django.forms.Charfield(required=False)
But in your code, probably you want to achieve value of the field:
field_value = form[field_name].value
If you have a data bounded field, you can change also data of form:
form.data(form.add_prefix(field_name)) = new_value
But this is already wrong approach to change something here.
I can imagine: in your case you don't understand how form / modelform saves the data and therefore you want to do something unnecessary:
Once more time, how ModelForm "saves" the data in DB:
Form validate the data.
Form clean the data.
on form.save(**kwargs) - Form create the instance and:
call instance.save() if form.save(commit=True)
dont call instance.save() if form.save(commit=False)
Instance saves the data.
Form don't save anything itself, instance.save - There is all what you need.
instance.save(self, force_insert=False, force_update=False, using=None, update_fields=None)
In your case you use CreateView - there all fields should be initiated, you can not UPDATE something, this something not exists yet. You can only avoid empty values, if you want.
def form_valid(self, form):
if form.has_changed():
form.cleaned_data = {key:val for key,val in form.cleaned_data.items() if val and key in form.changed_data}
return super().form_valid(form)
al last: form.instance.field is ridiculous
form has fields. You can achieve every field by name.
Model class has fields. You can achieve every field by name.
instance has attributes or properties. You can achieve every attribute by name.
And right now: what you are really want to do?
I want to have errors as a label above a field if it is not filled.
This is my views.py:
#login_required(login_url='user_profile:login')
def NewWriting(request):
if request.method=="POST":
form=WritingForm(request.POST)
if form.is_valid():
post=form.save(commit=False)
post.author=request.user
post.save()
return redirect('user_profile:index')
else:
form = WritingForm()
subject = Subject.objects.all()
return render(request,'user_profile/writing_form.html', {'form':form , 'subject':subject})
what should I add to my code?
Thanks
Without seeing your form class ...
Option 1:
If you really want the user to be able to submit the form with empty data and then specifically show them that error using the form, set the required=False kwarg for the specific field in your WritingForm class. Then override the clean_<fieldname> (link) method and then you could do:
def clean_<fieldname>:
if self.cleaned_data['<fieldname>'].strip() == '':
raise ValidationError('This field cannot be blank!')
return self.cleaned_data['<fieldname>']
Replacing <fieldname> with whatever that fieldname is.
Option 2:
The default for any form is to make all fields required (IE: required=True kwarg on the field). So in general, if the field is required most browsers will at least move the cursor to the empty field and won't allow the form to be submitted while there is no data in the field.
You also need to return a bound form in the case where form.is_valid() returns False or you won't ever see the errors (right now you don't return anything if the form is invalid). Please see the django docs here for a common functional view pattern using forms.
You need to add another all to render if the form is not valid, and in your template, you need to make use of form.errors. Something like this should work so that form validation errors are then passed back to the UI/template for display to the user:
#login_required(login_url='user_profile:login')
def NewWriting(request):
form = None
if request.method=="POST":
form=WritingForm(request.POST)
if form.is_valid():
post=form.save(commit=False)
post.author=request.user
post.save()
return redirect('user_profile:index')
if form is None:
form = WritingForm()
subject = Subject.objects.all()
return render(request,'user_profile/writing_form.html', {'form':form , 'subject':subject})
Basically, what I'd like to do is take a formset that has been bounded to request.POST, add a blank form to it, and return the new formset.
class MyView(View):
def post(self, request):
MyFormSet = formset_factory(MyForm)
posted_formset = MyFormSet(request.POST)
# Append a blank form to posted_formset here
return render(request, 'mytemplate.html', {'formset': posted_formset})
Is there any clean way to do this in Django 1.6? Right now I've got a workaround that involves creating a new formset with initial set to the data bound in posted_formset, but it's kind of clunky, and I feel like there should be an easier way to do this.
This might be a possible duplicate of this question: Django: How to add an extra form to a formset after it has been constructed?
Otherwise have a look at the extra parameter for the factory function. You get more information about formsets in the Django Documentation
So it seems like there's no clean way to do this, as after a formset is bound to a QueryDict, the extra parameter passed to formset_factory becomes irrelevant. Here's how I'm doing it in my view right now:
class MyView(View):
def post(self, request):
MyFormSet = formset_factory(MyForm)
posted_formset = MyFormSet(request.POST)
initial = []
for form in posted_formset:
data = {}
for field in form.fields:
data[field] = form[field].value()
initial.append(data)
formset = AuthorFormSet(initial=initial)
return render(request, 'mytemplate.html', {'formset': formset})
Basically, I bind a formset to request.POST, then iterate through it and grab the field values and put them into an array of dicts, initial, which I pass in when I make a new formset (see docs) that will have an extra blank form at the end because it's not bound.
This should work with any form and formset. Hope it helps someone.
What is the correct way to retrieve the value of a field before it is saved when the model form is submitted?
For instance, I am trying to get the field 'name' as it was before changed in the form. I was doing like below, and it works, but I am not sure that's the correct way to do it.
views.py:
if formset.is_valid():
for form in formset:
if form.has_changed and not form.empty_permitted:
cd = form.cleaned_data
new_fieldName = cd.get('name')
old_fieldName = str(cd.get('id'))
form.save()
Any suggestion?
formset.is_valid will call each form's is_valid method, which in turn will call full_clean, which calls _post_update, which updates the form's instance with the values submitted with the form. It would be too late to find references to the old values after you call formset.is_valid; you have two options:
Store copies of the instance fields before you call formset.is_valid:
saved = {}
for form in formset.forms:
saved[form.instance.pk] = form.instance.__dict__.copy()
# or keep only specific fields
Retrieve a fresh instance of the record before you call its save:
original = form._meta.model.objects.get(pk=form.instance.pk)
# compare original against form.instance and observe differences
# save your form when you're ready:
form.save()
You have also pre_save(). I use is_valid to validate any errors or restrictions to the fields and pre_save to automate processes.
Hope it helps.
In Django's admin, I've seen how I can set the fields of an 'add object' form using GET variables (e.g. /admin/app/model/add?title=lol sets the 'Title' field to 'lol').
However, I want to be able to do something along the lines of /admin/app/model/add?key=18 and load default data for my fields from an instance of another model. Ideally, I'd also like to be able to do some processing on the values that I populate the form with. How do I do this?
I managed to figure it out. Thankfully, Django allows you to replace a request's GET dict (which it uses to pre-populate the admin form). The following worked for me:
class ArticleAdmin(admin.ModelAdmin):
# ...
def add_view(self, request, form_url='', extra_context=None):
source_id = request.GET.get('source', None)
if source_id is not None:
source = FeedPost.objects.get(id=source_id)
# any extra processing can go here...
g = request.GET.copy()
g.update({
'title': source.title,
'contents': source.description + u"... \n\n[" + source.url + "]",
})
request.GET = g
return super(ArticleAdmin, self).add_view(request, form_url, extra_context)
This way, I obtain the source object from a URL parameter, do what I want with them, and pre-populate the form.
You can override method add_view of ModelAdmin instance. Add getting an object there, set object's pk to None and provide that object as an instance to the form. Object with pk == None will be always inserted as a new object in the database on form's save()