Setting the form value when the form is initiated - python

I am trying to set a field value when a form is initiated.
The value of this field is retrieved when we enter the view - the view being the timesheet. Then for each Time set in the view, I want to relate it back to the timesheet.
#login_required
#requires_csrf_token
def timesheet(request, timesheet_id):
timesheet = TimeSheet.objects.get(pk=timesheet_id)
NewTimeFormSet = modelformset_factory(Time, form=TimeForm, formset=RequiredFormSet)
if request.method == 'POST':
newtime_formset = NewTimeFormSet(request.POST, request.FILES)
for form in newtime_formset:
if form.is_valid():
form.save()
#then render template etc
So, to make sure the form validates I want to set this field when the form is initiated. When I try to set this field after POST in the view, I haven't been able to get the field to set or form to validate.
My code gets the timesheet_id when the model instance is initiated on entering the view
def __init__(self, *args, **kwargs):
# this allows it to get the timesheet_id
print "initiating a timesheet"
super(TimeSheet, self).__init__(*args, **kwargs)
And then the form is generated and I run the form init. So this is what I've tried
class TimeForm(forms.ModelForm):
class Meta:
model = Time
fields = ['project_id', 'date_worked', 'hours', 'description', 'timesheet_id',]
# some labels and widgets, the timesheet_id has a hidden input
def __init__(self, *args, **kwargs):
print "initiating form"
super(TimeForm, self).__init__(*args, **kwargs)
timesheet = TimeSheet.objects.get(id=timesheet_id)
self.fields['timesheet_id'] = timesheet
This raises the error
NameError: global name 'timesheet_id' is not defined
I don't know how to do this...
I've also attempted setting the field in the form clean() method, but it populates (shown by a print) and then still doesn't validate and I raise a formset error 'This field is required'.
Help!

You don't actually accept a timesheet_id parameter in the form init method, so that value is not defined hence the error.
However, this is the wrong approach. There is no point passing a value to a form, outputting it as a hidden field, then getting it back, when you had it all along. The way to do this is to exclude the value from the form's fields, and set it on save.
class TimeForm(forms.ModelForm):
class Meta:
model = Time
fields = ['project_id', 'date_worked', 'hours', 'description',]
...
if request.method == 'POST':
newtime_formset = NewTimeFormSet(request.POST, request.FILES)
if newtime_formset.is_valid():
for form in newtime_formset:
new_time = form.save(commit=False)
new_time.timesheet_id = 1 # or whatever
new_time.save()
Note, again, you should check the validity of the whole formset before iterating through to save; otherwise you might end up saving some of them before encountering an invalid form.

Related

How to validate a formset in dajngo

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.

Django admin site modify save

In the admin site, I have a custom form. However, I want to override the save method so that if a certain keyword is entered, I do not save it into the database. Is this possible?
class MyCustomForm (forms.ModelForm):
def save(self, commit=True):
input_name = self.cleaned_data.get('input_name', None)
if input_name == "MyKeyword":
//Do not save
else:
return super(MyCustomForm, self).save(commit=commit)
but this returns the error:
AttributeError 'NoneType' object has no attribute 'save'
Edit
Following this: Overriding the save method in Django ModelForm
I tried:
def save(self, force_insert=False, force_update=False, commit=True):
m = super(MyCustomCategoryForm, self).save(commit=False)
input_name = self.cleaned_data.get('input_name', None)
if input_name != "MyKeyword":
m.save()
return m
But the admin site still creates a new entry if the input_name is "MyKeyword"
The save() method is supposed to return the object, whether saved or not. It should rather look like:
def save(self, commit=True):
input_name = self.cleaned_data.get('input_name')
if input_name == "MyKeyword":
commit = False
return super().save(commit=commit)
However, as you can see here and here, the form is already called with commit=False and the object is saved later by the save_model() method of your ModelAdmin. This is the reason why you got AttributeError 'NoneType' object has no attribute 'save'. If you check the traceback of the exception, I'm pretty sure the error comes from this line.
Thus you should, in addition, override your ModelAdmin's save_model() method:
def save_model(self, request, obj, form, change):
if form.cleaned_data.get('input_name') == 'MyKeyword':
return # Don't save in this case
super().save_model(request, obj, form, change)
# And you'll also have to override the `ModelAdmin`'s `save_related()` method
# in the same flavor:
def save_related(self, request, form, formsets, change):
if form.cleaned_data.get('input_name') == 'MyKeyword':
return # Don't save in this case
super().save_related(request, form, formsets, change)
Given your comment in this answer
It is working now but I just have one related question. The admin site will still display a green banner at the top saying "The model was added successfully". Can I override some method so that it is red and says "The model already exists"?
It looks that what you want to do is not overriding the save method but controlling form validation. Which is completely different. You just have to override your form's clean method.
from django import forms
def clean(self):
"""
super().clean()
if self.cleaned_data.get('input_name') == 'MyKeyword':
raise forms.ValidationError("The model already exists")
Or, even better since it looks like you want to clean a single field:
from django import form
def clean_input_name(self):
data = self.cleaned_data['input_name']
if data == 'MyKeyWord':
raise forms.ValidationError("The model already exists")
However, I guess input_name is also a field of your model and you don't want to raise this error only on one form but across all your project. In which case, what you are looking for are Model validators:
from django.core.exceptions import ValidationError
def validate_input_name(value):
if value == 'MyKeyword':
raise ValidationError("The model already exists")

Django formset cleaned_data empty when submitted form unchanged

I've been experiencing a weird problem, regarding Django 1.4 and formsets: when the submitted data is unchanged, the cleaned_data field of the formset is empty, even if the formset itself passes the validation.
Here is an example:
forms.py:
class NameForm(forms.Form):
name = forms.CharField(required=False, initial='Foo')
views.py:
def welcome(request):
Formset = formset_factory(NameForm, extra=1)
if request.method == 'POST':
formset = Formset(request.POST)
print '1.Formset is valid?', formset.is_valid()
print '2.Formset', formset
print '3.Formset cleaned_data', formset.cleaned_data
else:
formset = Formset()
return render_to_response('template.html', locals())
Although formset is valid, and it actually contains data, line 3 prints a list of an empty dictionary, unless I've actually changed the initial value in the field.
This seems weird to me, but I'm probably doing something wrong. Any help?
A formset renders bunch of forms as well as several hidden input fields holding info such as max number of forms. Thus a correctly POSTed formset always contains data.
And, the initial 'Foo' inside CharField name is the reason that formset got empty dictionary. When empty_permitted of a form is set to True, and all items of the form equals to its initial value, Django will treat the form as empty and set its cleaned_data to be empty dict. The empty_permitted defaults to False, formset will set it to True for extra forms. Thus after you clicked submit w/o editing the value 'Foo', the form instance was treated as empty, hence the formset wrapping the form got an empty cleaned_data.
This happened to me. #okm is correct but it's not clear from his answer how to fix it. See this answer for a solution:
class MyModelFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(MyModelFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False

Errors on validating a custom Form with an optional argument in a POST request in Django

So I have this custom ModelForm that I created that takes in a variable creator_list for the queryset like this:
class UserModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.get_full_name()
class OrderCreateForm(ModelForm):
class Meta:
model=Order
fields=('work_type', 'comment',)
def __init__(self, creator_list=None, *args, **kwargs):
super(OrderCreateForm, self).__init__(*args, **kwargs)
if creator_list:
self.fields['creator'] = UserModelChoiceField(
queryset=creator_list,
empty_label="Select a user",
widget=Select(attrs={
'onchange': "Dajaxice.doors.orders_create_creator_changed(fill_other_fields, {'creator_pk': this.options[this.selectedIndex].value})"
})
)
self.fields['place'] = UserModelChoiceField(
queryset=User.objects.none(),
empty_label="Select a creator first"
)
When I am simply displaying the fields, everything works perfectly. However during a POST submission. I get errors that I don't know how to debug.
My views.py looks like this:
user = request.user
dictionary = get_order_create_dictionary(user)
if request.method == 'POST':
#import ipdb; ipdb.set_trace()
form = OrderCreateForm(request.POST)
if form.is_valid():
creator = form.cleaned_data['creator']
place = form.cleaned_data['place']
work_type = form.cleaned_data['work_type']
comment = form.cleaned_data['comment']
new_order = Order.objects.create(
creator =creator,
place =place,
work_type=work_type,
comment =comment
)
messages.success(request, "Your order #{} had been created!".format(new_order.pk))
logger.info("{user} created order #{pk}".format(user=user, pk=new_order.pk))
return HttpResponseRedirect(reverse('orders_detail', kwargs={'pk': new_order.pk}))
else:
return render(request, 'doors/orders/create.html', {'form': form, 'can_assign_creator': dictionary['can_assign_creator']})
else:
if dictionary:
return render(request, 'doors/orders/create.html', {
'form': OrderCreateForm(creator_list=dictionary['creator_list']),
'can_assign_creator': dictionary['can_assign_creator']
})
else:
return HttpResponseRedirect(reverse('orders_list'))
get_order_create_dictionary() simply returns a dictionary that looks like this:
dictionary = {
'creator_list': Order.objects.all(), # Or some kind of filtered Order.
'can_assign_order: 1, # Or 0. This variable is used in the template to control access to what gets displayed.
}
Currently with the above code I get an error like this when I try to POST something:
AttributeError: 'QueryDict' object has no attribute 'all'
on the line "return render(request, 'doors/orders/create.html', {'form': form, 'can_assign_creator': dictionary['can_assign_creator']})"
I thought it has something to do with the line form = OrderCreateForm(request.POST) so I changed that to form = OrderCreateForm(request.POST, creator_list=dictionary['creator_list']). But then I get this error:
TypeError: __init__() got multiple values for keyword argument 'creator_list'
on the line "form = OrderCreateForm(request.POST, creator_list=dictionary['creator_list'])"
I have no clue how to resolve this. I appreciate any help or tips! Thanks!
EDIT:
I changed the line to form = OrderCreateForm(dictionary['creator_list'], request.POST) and now the validation works, but it won't let me submit a valid POST. It keeps saying Select a valid choice. That choice is not one of the available choices. for the place. This probably has something to do with how I populate the <option> with place using Ajax depending on what the creator is.
You'd better instantiate Form instances with only named arguments, i.e.
form = OrderCreateForm(creator_list=dictionary['creator_list'], data=request.POST)
One exception is when form only has one argument - the data. This will help you to avoid messing up with arguments order (which is the reason of your errors here).

Django: Edit Function while not changing the Image data

I have an edit function that I want the user to be able to edit the Picture object (tags), while keeping the old image. The form is looking for a photo but I do want the user to be able to change the image - just the other information.
How do you pass the original image data from the picture object into the PictureForm so it validates?
My view:
#csrf_protect
#login_required
def edit_picture(request, picture_id, template_name="picture/newuserpicture.html"):
picture = get_object_or_404(Picture, id=picture_id)
if request.user != picture.user:
return HttpResponseForbidden()
if request.method == 'POST':
form = PictureForm(request.POST or None, request.FILES or None, instance=picture)
if form.is_valid():
form.save()
return HttpResponseRedirect('/picture/%d/' % picture.id )
else:
form = PictureForm(instance=picture)
data = { "picture":picture, "form":form }
return render_to_response(template_name,
data,
context_instance=RequestContext(request))
I think this thread should give you a clue how to make existing fields readonly:
In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?
I you want to hide the picture completely and stumble across validation errors because the field is marked as required in your model definition (blank=True) another option would be to override the form's save method and tweak the field's required attribute.
Something along these lines:
def __init__(self, *args, **kwargs):
super(PictureForm, self).__init__(*args, **kwargs)
for key in self.fields:
self.fields[key].required = False
I guess I am not sure how to just comment on the original question, but what do you mean by validate? If you are just needing to ensure that the picture object's picture is actually the same after the form is done can you not make some custom clean methods for the form? in a clean method you could compare all metadata and the image then proceed to the forms save.

Categories

Resources