Django request.POST as an argument of a form - django

I am having a hard time wrapping my head around what request.POST is doing as a argument in the following example:
def addauthorView(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
first_name = form.cleaned_data['firstname']
last_name = form.cleaned_data['lastname']
user_email = form.cleaned_data['email']
c = AuthorModel(firstname=first_name, lastname=last_name, email=user_email)
c.save()
return HttpResponseRedirect('thanks/')
else:
form = ContactForm(request.POST)
return render(request, 'addauthor.html', {'form': form})
So I know that this works, but for some reason I cannot understand the magic that is happening with form = ContactForm(request.POST). Why does the ContactForm need the request.POST argument? What is happening behind the scenes?
Extra question, why is form = ContactForm(request.POST) then repeated in the else: block. Why is that helpful and when is that useful? Examples?

In a nutshell, request.POST is simply the data that was sent when the form was submitted. It's a dictionary of what the user submitted for firstname, lastname and email in your code sample. For those that come from a PHP background, it's what is provided in $_POST.
form = ContactForm(request.POST) binds the data to the form class so Django can do fun stuff like validate inputs with is_valid().
Why then, would you add request.POST to the else: block? Well, have you ever submitted a form to a website and when there was an error you had to completely fill out the form again? That's a crappy user experience, right? By sending the form back to the user with the data from request.POST, you can re-render what the user inputed - along with helpful extras such as error messages - so they can fix them and resubmit.
EDIT: To expand, here is the init method from the BaseForm class in Django:
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False):
self.is_bound = data is not None or files is not None
self.data = data or {}
self.files = files or {}
self.auto_id = auto_id
self.prefix = prefix
self.initial = initial or {}
self.error_class = error_class
# Translators: This is the default suffix added to form field labels
self.label_suffix = label_suffix if label_suffix is not None else _(':')
self.empty_permitted = empty_permitted
self._errors = None # Stores the errors after clean() has been called.
self._changed_data = None
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
self.fields = copy.deepcopy(self.base_fields)
When you pass request.POST to your form class, you're really doing data=request.POST. That in turn triggers the self.is_bound = True

Related

Pre-populating Model Form with object data - Django

I have tried various options for this but no luck so far. I am trying to get instance data to be pre-populated into my ModelField. Here is what I have:
forms.py
class edit_project_info(ModelForm):
project_name = forms.CharField(max_length=150)
class Meta:
model = Project
exclude = ['project_type', 'created_date', 'start_date', 'end_date', 'pm_scope', 'dev_scope', 'design_scope', 'testing_scope' ]
View.py
def edit_project (request, offset):
this_project = Project.objects.get(pk=offset)
data = {'project_name' : 'abc'}
if request.method == 'POST':
form = edit_project_info(request.POST, instance=this_project, initial=data)
if form.is_valid():
form.save()
return HttpResponseRedirect('/project_profile/%s/' % offset)
else:
form = edit_project_info()
All I get is an empty field. I can add the initial value to forms.py, but then it is static rather than populated based on the form instance. What I have done here with creating a dict and then passing it to initial in the form instance does not seem to do anything. I'm sure I am missing something basic. Any help would be great! Thanks ahead of time.
Two last lines recreate your form variable. Just remove else: form = edit_project_info():
def edit_project (request, offset):
this_project = Project.objects.get(pk=offset)
data = {'project_name' : 'abc'}
form = edit_project_info(request.POST, instance=this_project, initial=data)
if request.method == 'POST':
if form.is_valid():
form.save()
return HttpResponseRedirect('/project_profile/%s/' % offset)
# else:
# form = edit_project_info()
# ...

django raising KeyError when accessing to field in self.initial

I've wrote a form called AnswerForm. This form is used to get an answer from the choices of the question.(Choice and Question are models). Here is my code to AnswerForm:
class AnswerForm(forms.Form):
question = forms.ModelChoiceField(queryset=Question.objects.all(),
required=True,
widget=forms.HiddenInput(attrs={"class": "hidden-input"}))
def __init__(self, *args, **kwargs):
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['answer'] = forms.ModelChoiceField(queryset=self.initial["question"].choice_set.all(),
required=False,
widget=ExamChoiceInput)
def clean_question(self):
return self.initial["question"]
I use this form in a formset so user can answer many questions at one time. However when the users sends the form a KeyError value is raised for self.initial["question"] in second line of init. (I've printed self.initial and it's completely empty). Here is the function in which I process the request with post method:
def process_saving_form(request):
if request.method == "POST":
current_user = request.user
AnswerFormSet = formset_factory(AnswerForm)
formset = AnswerFormSet(request.POST)
if formset.is_valid():
for form in formset:
new_user_madechoice = MadeChoice(
user=current_user,
choice=form.cleaned_data["answer"]
)
try:
current_user_madechoice = current_user.madechoice_set.get(choice__question=form.cleaned_data["question"])
if current_user_madechoice.choice != form.cleaned_data["answer"]:
current_user_madechoice.delete()
new_user_madechoice.save()
except MadeChoice.DoesNotExist:
new_user_madechoice.save()
May you tell me what's the problem?
The problem is that you did not pass any initial data to you AnswerFormSet.
See how I did it in the answer to your previous question:
initial = [{'question': q} for q in questions]
formset = AnswerFormSet(request.POST, initial=initial)

Django form is_valid() fails

I am a real beginner in web development. The following code is failing at the is_valid() check. But I do not understand why: The form should get its data filled from the POST-data or not?
Model:
class Statement(models.Model):
text = models.CharField(max_length=255)
user = models.ForeignKey(User)
time = models.DateField()
views = models.IntegerField()
ModelForm:
class StatementForm(ModelForm):
class Meta:
model = Statement
widgets = {
'time':forms.HiddenInput(),
'user':forms.HiddenInput(),
'views':forms.HiddenInput(),
}
View function:
def new(request):
if request.method == 'POST': # If the form has been submitted...
form = StatementForm(request.POST) # A form bound to the POST data
if form.is_valid():
stmt = form.save()
path = 'stmt/' + stmt.id
return render_to_response(path, {'stmt': stmt})
else:
c = {}
c.update(csrf(request))
loggedin_user = request.user
d = datetime.now()
form = StatementForm(request.POST, initial={'time': d.strftime("%Y-%m-%d %H:%M:%S"), 'user':loggedin_user, 'views':0})
return render_to_response('new_stmt.html', {'form': form, },context_instance=RequestContext(request))
I found similar topics and tried a lot. This is how i think it should work. I really need advice.
All fields of your model are required. So, form.is_valid() will be True, if all fields are filled with correct values and are not blanked.
You have declared fields time, user, views as hidden fields. Are you sure, that you have filled them in your template form?
Also, you may want to auto stamp field time = models.DateField(). Modify your model field like
time = models.DateField(auto_now=True)`.
After this you don't have to fill it by yourself in template form.
Your view must return HttpResponse object in all cases. If your form is not valid, i.e. if form.is_valid() will return False, then no HttpResponse object will be returned by your view. This can be the source of your fail. Add else statement for if form.is_valid():
from django.http import Http404
def new(request):
if request.method == 'POST': # If the form has been submitted...
form = StatementForm(request.POST) # A form bound to the POST data
if form.is_valid():
stmt = form.save()
path = 'stmt/' + stmt.id
return render_to_response(path, {'stmt': stmt})
else:
# Do something in case if form is not valid
raise Http404
else:
# Your code without changes
Change this line:
form = StatementForm(request.POST, initial={'time': d.strftime("%Y-%m-%d %H:%M:%S"), 'user':loggedin_user, 'views':0})
For this:
form = StatementForm(initial={'time': d.strftime("%Y-%m-%d %H:%M:%S"), 'user':loggedin_user, 'views':0})

Pre-Setting django form data before displaying the form

I am displaying a django form and I want to prepare some field data before it
is passed to to be rendered. In the django docs, I see plenty of places
where form data is accessed, but none where form data is set before display.
Any thoughts or suggestions on how to do this?
Here's an example similar to the django docs.
-----------forms.py--------------
class BookForm(ModelForm):
author = forms.CharField(max_length=100)
title = forms.CharField(max_length=3,
widget=forms.Select(choices=TITLE_CHOICES))
birth_date = forms.DateField(required=False)
-----------views.py--------------
def author_view(request):
if request.method == 'POST':
# DO My processing...
form = BookForm()
# How can I edit, or preset my form fields here?
c = Context({
'form': form,
})
return prepCxt(request, 'book.html', c) # Wrapper for easy display
In your views you have:
def author_view(request):
if request.method == 'POST':
# DO My processing...
form = BookForm()
# How can I edit, or preset my form fields here?
c = Context({
'form': form,
})
return prepCxt(request, 'book.html', c) # Wrapper for easy display
You should move your form=BookForm() before the if:
def author_view(request):
form = BookForm()
if request.method == 'POST':
# DO My processing...
What happens is that the if "POST" section adds a value in form and then it could get overriden.
Secondly if you are trying to change something in the way it display you are probably best adding default/initial values:
Django set default form values:
BookForm(initial={ 'myfield': 'myval'})
if you are tryiong to change values that you want to save to the DB then you:
if form.is_valid():
myobject = form.save(commit=false)
myobject.myfield = mval
myobj.save()
form = BookForm(instance = myobjext)
Something else? Please be more specific.

Django Form field initial value on failed validation

how do I set the value of a field element after a form has been submitted but has failed validation? e.g.
if form.is_valid():
form.save()
else:
form.data['my_field'] = 'some different data'
I don't really want to put it in the view though and would rather have it as part of the form class.
Thanks
The documentation says:
If you have a bound Form instance and want to change the data somehow, or if you want to bind an unbound Form instance to some data, create another Form instance. There is no way to change data in a Form instance. Once a Form instance has been created, you should consider its data immutable, whether it has data or not.
I cannot really believe that your code works. But ok. Based on the documentation I would do it this way:
if request.method == 'POST':
data = request.POST.copy()
form = MyForm(data)
if form.is_valid():
form.save()
else:
data['myField'] = 'some different data'
form = MyForm(initial=data)
I ended up doing
if request.method == 'POST':
new_data = request.POST.copy()
form = MyForm(data=new_data)
if form.is_valid():
form.save()
else:
new_data['myField'] = 'some different data'
Hope this helps someone
You can put it in the form class like this:
class MyForm(forms.Form):
MY_VALUE = 'SOMETHING'
myfield = forms.CharField(
initial=MY_VALUE,
widget=forms.TextInput(attrs={'disabled': 'disabled'})
def __init__(self, *args, **kwargs):
# If the form has been submitted, populate the disabled field
if 'data' in kwargs:
data = kwargs['data'].copy()
self.prefix = kwargs.get('prefix')
data[self.add_prefix('myfield')] = MY_VALUE
kwargs['data'] = data
super(MyForm, self).__init__(*args, **kwargs)
The way it works, is it tests to see if any data has been passed in to the form constructor. If it has, it copies it (the uncopied data is immutable) and then puts the initial value in before continuing to instantiate the form.