I'm trying to run a simple update form that should update all object values in DB submitted from the form.
This is my update view that does nothing other than redirecting to the "/". No errors but no update either.
def update(request, business_id):
if request.method == 'POST':
form = BusinessForm(request.POST)
if form.is_valid():
t = Business.objects.get(id=business_id)
t.save()
return HttpResponseRedirect("/")
else:
...
You are not updating any fields, use form.cleaned_data to get the form field values:
Once is_valid() returns True, the successfully validated form data
will be in the form.cleaned_data dictionary. This data will have been
converted nicely into Python types for you.
if form.is_valid():
t = Business.objects.get(id=business_id)
t.my_field = form.cleaned_data['my_field']
t.save()
Also, consider using an UpdateView class-based generic view instead of a function-based:
A view that displays a form for editing an existing object,
redisplaying the form with validation errors (if there are any) and
saving changes to the object. This uses a form automatically generated
from the object’s model class (unless a form class is manually
specified).
Related
I am using formset to input my data into the database but for some reason it just doesn't validate, whenever I test in the terminal and call the .is_valid() It just returns false no matter what I try. Here's the code in my views.py and forms.py . Any help will be much appreciated!
# Advanced Subjects (Advanced Biology)
def form_5_entry_biology_view(self, request):
current_teacher = User.objects.get(email=request.user.email)
logged_school = current_teacher.school_number
students_involved = User.objects.get(school_number=logged_school).teacher.all()
data = {"student_name": students_involved}
formset_data = AdvancedStudents.objects.filter(class_studying="Form V", combination="PCB")
student_formset = formset_factory(AdvancedBiologyForm, extra=0)
initial = []
for element in formset_data:
initial.append({"student_name": element})
formset = student_formset(request.POST or None, initial=initial)
print(formset.is_valid())
context = {
"students": students_involved,
"formset": formset,
"class_of_students": "Form V",
"subject_name": "Advanced Biology",
}
return render(request, "analyzer/marks_entry/marks_entry_page.html", context)
And here is my forms.py
class AdvancedBiologyForm(forms.ModelForm):
student_name = forms.CharField()
class Meta:
model = ResultsALevel
fields = ('student_name', 'advanced_biology_1', 'advanced_biology_2',
'advanced_biology_3',)
Before using request.POST and is_valid() you probably want to check if there actually is a post request or if the page is just viewed:
def form_5_entry_biology_view(self, request):
current_teacher = User.objects.get(email=request.user.email)
logged_school = current_teacher.school_number
students_involved = User.objects.get(school_number=logged_school).teacher.all()
data = {"student_name": students_involved}
formset_data = AdvancedStudents.objects.filter(class_studying="Form V", combination="PCB")
# Here you are creating the formset using the model
student_formset = formset_factory(AdvancedBiologyForm, extra=0)
# Here you are generating your initial data
initial = []
for element in formset_data:
initial.append({"student_name": element})
# Here you are using the initial data to create pre-populated
# forms with it using the formset.
# These forms will be displayed when the page loads.
formset = student_formset(initial=initial)
context = {
"students": students_involved,
"formset": formset,
"class_of_students": "Form V",
"subject_name": "Advanced Biology",
}
# But if the user hits the "submit"-Button...
if request.method == 'POST':
# ... you don't want to have the formset with your
# initial data. Instead you want the entries by the user
# which are transmitted in request.POST to populate the
# formset forms.
formset = student_formset(request.POST or None)
# Now you can validate the formset with the fields which
# got input the the user; not the "initial" data like in
# your original code
if formset.is_valid():
# This runs formset validation.
# You can define your own formset validations like
# you would for models/forms.
for form in formset:
# And / Alternatively:
# you could in theory also add another "if form.is_valid():" in here
# This would trigger any validation on the
# model/form; not the validators on the formset.
form.save()
return HttpResponseRedirect(...
return render(request, "analyzer/marks_entry/marks_entry_page.html", context)
Otherwise you might call is_valid() on an unbound form.
From Django docs:
If the form is submitted using a POST request, the view will once again create a form instance and populate it with data from the request: form = NameForm(request.POST) This is called “binding data to the form” (it is now a bound form).
Basically if a form is empty it's unbound, and if it's populated with data it gets bound after POST. When you open a page and immediately try "is_valid()" it will basically always be false as you are checking if an empty form is valid; which it likely never is.
To point out the error:
formset = student_formset(request.POST or None, initial=initial)
print(formset.is_valid())
This is not valid. Because initial values are not equal to populating a form field with "real" values. So what happens is that it tries to populate the fields in the form with request.POST or None.
But you don't have a if request.method == 'POST': condition. So your code will just run through before hitting the last line of code which is the return statement displaying the page.
This means that your code validates request.POST or None before the user even saw the page. So there is no way a user could have already entered data and hit submit. Which means there is no POST-request, so it always turns None. So you are basically calling is_valid() on a form that has no field values in it which leads to the validation failing.
EDIT 1: I just noticed in your forms.py you have written:
fields = ('student_name', 'advanced_biology_1', 'advanced_biology_2',
'advanced_biology_3',)
This should be a list instead:
fields = ['student_name', 'advanced_biology_1', 'advanced_biology_2',
'advanced_biology_3',]
EDIT 2: fixed wrong variable name
EDIT 3: added extensive comments to clarify what's happening in the code
EDIT 4: pointed out cause of problem more distinctly.
Let's say I have a Django form with ChoiceField:
class MyForm(forms.Form):
name = forms.CharField(label="Name", required=True)
some_object = forms.ChoiceField(label="Object", choices=[(0, '-----')] + [(x.id, x.name) for x in Obj.objects.all()])
Choisefield is being initialized with list of objects headed by 'empty choice'. There is no object with pk=0.
In that form I have a clean() method:
def clean(self):
if not Obj.objects.filter(self.cleaned.data['some_object'].exists():
self.errors.update({'some_object': ['Invalid choice']})
It works well when I'm sending a form to a server, if data in it doesn't match conditions, field.is_valid returns False and I render form with error messages. But, when I create an empty form like this:
if request.method == 'GET':
data = {'name': 'Untitled',
'some_object': 0}
form = MyForm(data)
return render(request, 'app\template.html', {'form': form})
Django renders form with error message ('Invalid choice') even though form was just created and 'Object' select was intended to be set in empty position. Is it possible to disable form clean() method in specific cases? Or maybe I'm doing this all wrong? What is the best practice to create an empty form?
The problem is that a form with any data dictionary passed to it counts as a "bound" form, against which Django will perform data validation. See here for details: https://docs.djangoproject.com/en/2.1/ref/forms/api/#bound-and-unbound-forms
You want an "unbound" form - to have default initial values in here, just set the initial property on your form fields. See full details here: https://docs.djangoproject.com/en/2.1/ref/forms/fields/#initial
How can i capture and use the data of specific field, that was entered into my formset(on template), before saving it to database?
I can get the "hour" value via .save(commit=False) because the value didn't change, but how can I capture the value that changed ( for example, data from formset.work_amount after user enters it)?
Edit 2. I forgot to mention i'm using a model formset
AuthorFormSet = modelformset_factory(CalendGraph, fields=('project_name','work_name','work_amount','unit_name','employee_name','hours','days','approve_need','approve_time','calendar_start','calendar_end',),extra =1, can_delete=True)
if request.method == 'POST':
formset = AuthorFormSet(request.POST, request.FILES)
new_formset= formset.save(commit=False)
if formset.is_valid():
worka = int(request.POST.get('work_amount') #wrong, how can i get the work amount data from formset?
new_formset.hours= worka*8
new_formset.save()
return HttpResponseRedirect("/calend-graph-1/")
This question doesn't quite make sense: a formset contains multiple forms, so you will have multiple values for work_amount. You get each value from the cleaned_data dict of each form in the formset, so you could get all the work_amount values with:
[form.cleaned_data['work_amount'] for form in formset]
I have a page with a list of the same forms. Each form has a different value for a hidden field to distinguish the submissions. If I submit one of the forms, the page is refreshed and all the same forms are shown. However, each form now has it's fields populated with the data submitted on the last request. I want each form's fields to be empty no matter what. I tried supplying initial='' to the field in the form object; this didn't work.
Ideas?
Thanks
If you want to use a list of same forms, you should use formsets.
Overriding POST initial values:
form = SomeForm()
if request.method == 'POST':
form = SomeForm(request.POST)
if form.is_valid():
#do something and redirect somewhere
#next line will be reached only if form is invalid
#and you want to override POST data
form = SomeForm()
return render(.....)
How can I clean the data in a form and have the cleaned data redisplayed instead of the submitted data?
There are several fields in my form, and every time the user submits it, it should be redisplayed with the values the user entered. However, some of the fields I would like to clean and update for the user. More specifically, I have a field FriendlyIntegerField(forms.CharField) in which I override to_python to not only call int(str(value)), but also set any negative number to 0 etc. I do not want to redisplay the form with the invalid data and have the user fix it himself (which is how Django wants me to do it).
I don't have a problem cleaning the data and use it for the rest of my view-function, but how can I update the actual form with this data?
By the way, the form does not reflect a structure in my data model, and so inherits from Form, not ModelForm.
Edit:
My Field (in a stripped down version) looks like this:
class FriendlyIntegerField(forms.CharField):
def to_python(self, value):
try:
return str(int(str(value).replace(' ','')))
except:
raise forms.ValidationError('some error msg')
My Form (in a stripped down version) looks like this:
class SearchForm(forms.Form):
price_from = FriendlyIntegerField()
price_to = FriendlyIntegerField()
And my view:
def search(request, key):
if request.method == 'POST':
form = SearchForm(request.REQUEST)
if not form.is_valid():
print "Form not valid"
else:
form = SearchForm()
return render_to_response('path_to_template', {'form' : form}
If, after you've cleaned your form with is_valid(), you render that cleaned form with your view, rather than redirect to a new page, you'll see the cleaned data in your page.
(If you wanted the user to see this cleaned data and then properly submit it, you could use a hidden field to track whether the form data has already been cleaned, but this isn't without complications...)