Missing cleaned_data in forms (django) - python

I would like to create a form and the validation_forms that would check if some text apears in a box if another box has been checked correctly,
class Contact_form(forms.Form):
def __init__(self):
TYPE_CHOICE = (
('C', ('Client')),
('F', ('Facture')),
('V', ('Visite'))
)
self.file_type = forms.ChoiceField(choices = TYPE_CHOICE, widget=forms.RadioSelect)
self.file_name = forms.CharField(max_length=200)
self.file_cols = forms.CharField(max_length=200, widget=forms.Textarea)
self.file_date = forms.DateField()
self.file_sep = forms.CharField(max_length=5, initial=';')
self.file_header = forms.CharField(max_length=200, initial='0')
def __unicode__(self):
return self.name
# Check if file_cols is correctly filled
def clean_cols(self):
#cleaned_data = super(Contact_form, self).clean() # Error apears here
cleaned_file_type = self.cleaned_data.get(file_type)
cleaned_file_cols = self.cleaned_data.get(file_cols)
if cleaned_file_type == 'C':
if 'client' not in cleaned_file_cols:
raise forms.ValidationError("Mandatory fields aren't in collumn descriptor.")
if cleaned_file_type == 'F':
mandatory_field = ('fact', 'caht', 'fact_dat')
for mf in mandatory_field:
if mf not in cleaned_file_cols:
raise forms.ValidationError("Mandatory fields aren't in collumn descriptor.")
def contact(request):
contact_form = Contact_form()
contact_form.clean_cols()
return render_to_response('contact.html', {'contact_form' : contact_form})
Infortunatly, django keeps saying me that he doesn't reconize cleaned_data. I know i've missed something about the doc or something but i cannot get the point on what. Please help !

When validating an individual field, your clean method should have a name of the form
clean_<name of field>
for example clean_file_col. Then it will be called automatically when you do form.is_valid() in your view.
Naming your method clean_cols suggests that you have a field named cols, which could cause confusion.
In this case, your validation relies on other fields, so you should rename your clean_col method to simply clean. That way it will be called automatically when you do form.is_valid() in your view.
def clean(self):
cleaned_data = super(Contact_form, self).clean()
cleaned_file_type = self.cleaned_data.get(file_type)
# ...
Finally, in your view, you have not bound your form to any data,
contact_form = Contact_form()
so contact_form.is_valid() will always return False. You need to bind your form to the post data with form = ContactForm(request.POST). See the Django docs for using a form in a view for a full example and explanation.

Related

Django - Questionnaire - ModelFormSet - Cannot assign

Alright, so I'm following a tutorial on creating a small questionnaire with Django.
A User can create a survey with different multiple choice questions. Since they're multiple choice questions, users can also set the options for the questions.
A "survey taker" can then start a survey, select his or her preferred questions and submit the form.
I would like to only show a single question per page so I'm trying to work with modelformset_factory and implement pagination using the build in paginator.
The form is rendered correctly and a user can submit an answer, however, the form fails before "formset.is_valid()", I just can't figure out why;
ValueError at /surveys/1/submit/4/
Cannot assign "'2'": "Answer.option" must be a "Option" instance.
So I can not save an integer and I somehow have to relate the integer with the id from the option model... But I can't access cleaned_data yet so I guess I'm missing something here. Did I forget something within the formset?
I've been staring at this for a while now so any help is appreciated.
#Views
def submit(request, survey_pk, sub_pk):
# Let's retrieve the survey which is created by the survey-taker
try:
survey = Survey.objects.prefetch_related("question_set__option_set").get(
pk=survey_pk, is_active=True
)
except Survey.DoesNotExist:
raise Http404()
try:
submission = survey.submission_set.get(pk=sub_pk, is_complete=False)
except Submission.DoesNotExist:
raise Http404()
# Retrieve all question associated with this survey
questions = survey.question_set.all()
# Get all related options from the questions
options = [q.option_set.all() for q in questions]
form_kwargs = {"empty_permitted": False, "options": options,}
# Setup a formset utilizing a ModelFormSet
AnswerFormset = modelformset_factory(
Answer,
form=AnswerModelForm,
formset=BaseAnswerFormSet,
exclude=(),
extra=len(questions),
)
if request.method == "POST":
print("Request.POST", request.POST)
# This is where the problem starts
formset = AnswerFormset(request.POST, form_kwargs=form_kwargs)
print("Formset: ", formset)
if formset.is_valid():
print("Formset is valid")
with transaction.atomic():
for form in formset:
Answer.objects.create(
option_id=form.cleaned_data["option"],
submission_id=sub_pk,
)
submission.is_complete = True
submission.save
return redirect("survey-thanks", pk=survey_pk)
else:
formset = AnswerFormset(form_kwargs=form_kwargs)
print(form_kwargs)
question_forms = zip(questions, formset)
return render(
request,
"survey/submit.html",
{
"survey": survey,
"question_forms": question_forms,
"formset": formset
}
)
#forms
# Setup a Modelformset
class AnswerModelForm(forms.ModelForm):
class Meta:
model = Answer
fields = "option",
def __init__(self, *args, **kwargs):
options = kwargs.pop("options")
# Options must be a list of Option objects
choices = {(o.pk, o.text) for o in options}
super().__init__(*args, **kwargs)
option_field = forms.ChoiceField(choices=choices, widget=forms.RadioSelect, required=True)
self.fields["option"] = option_field
class BaseAnswerFormSet(forms.BaseFormSet):
def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
kwargs["options"] = kwargs["options"][index]
return kwargs
#models
class Answer(models.Model):
"""An answer a survey's questions."""
submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
option = models.ForeignKey(Option, on_delete=models.CASCADE)
option_id=form.cleaned_data["option"],
is the suspect, it may require an option instance, even though you are trying to pass the id. Try changing option_id to option__id and explicitly setting the form.cleaned_data["option"] to int. Another way to to is to pull the option manually and pass it directly:
option = Option.object.get(id=int(form.cleaned_data["option"]))

Django: Pass a variable/parameter to form from view? [duplicate]

I have a Model as follows:
class TankJournal(models.Model):
user = models.ForeignKey(User)
tank = models.ForeignKey(TankProfile)
ts = models.IntegerField(max_length=15)
title = models.CharField(max_length=50)
body = models.TextField()
I also have a model form for the above model as follows:
class JournalForm(ModelForm):
tank = forms.IntegerField(widget=forms.HiddenInput())
class Meta:
model = TankJournal
exclude = ('user','ts')
I want to know how to set the default value for that tank hidden field. Here is my function to show/save the form so far:
def addJournal(request, id=0):
if not request.user.is_authenticated():
return HttpResponseRedirect('/')
# checking if they own the tank
from django.contrib.auth.models import User
user = User.objects.get(pk=request.session['id'])
if request.method == 'POST':
form = JournalForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
# setting the user and ts
from time import time
obj.ts = int(time())
obj.user = user
obj.tank = TankProfile.objects.get(pk=form.cleaned_data['tank_id'])
# saving the test
obj.save()
else:
form = JournalForm()
try:
tank = TankProfile.objects.get(user=user, id=id)
except TankProfile.DoesNotExist:
return HttpResponseRedirect('/error/')
You can use Form.initial, which is explained here.
You have two options either populate the value when calling form constructor:
form = JournalForm(initial={'tank': 123})
or set the value in the form definition:
tank = forms.IntegerField(widget=forms.HiddenInput(), initial=123)
Other solution: Set initial after creating the form:
form.fields['tank'].initial = 123
If you are creating modelform from POST values initial can be assigned this way:
form = SomeModelForm(request.POST, initial={"option": "10"})
https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/#providing-initial-values
I had this other solution (I'm posting it in case someone else as me is using the following method from the model):
class onlyUserIsActiveField(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(onlyUserIsActiveField, self).__init__(*args, **kwargs)
self.fields['is_active'].initial = False
class Meta:
model = User
fields = ['is_active']
labels = {'is_active': 'Is Active'}
widgets = {
'is_active': forms.CheckboxInput( attrs={
'class': 'form-control bootstrap-switch',
'data-size': 'mini',
'data-on-color': 'success',
'data-on-text': 'Active',
'data-off-color': 'danger',
'data-off-text': 'Inactive',
'name': 'is_active',
})
}
The initial is definded on the __init__ function as self.fields['is_active'].initial = False
As explained in Django docs, initial is not default.
The initial value of a field is intended to be displayed in an HTML . But if the user delete this value, and finally send back a blank value for this field, the initial value is lost. So you do not obtain what is expected by a default behaviour.
The default behaviour is : the value that validation process will take if data argument do not contain any value for the field.
To implement that, a straightforward way is to combine initial and clean_<field>():
class JournalForm(ModelForm):
tank = forms.IntegerField(widget=forms.HiddenInput(), initial=123)
(...)
def clean_tank(self):
if not self['tank'].html_name in self.data:
return self.fields['tank'].initial
return self.cleaned_data['tank']
If you want to add initial value and post other value you have to add the following :
or None after request.POST
form = JournalForm(request.POST or None,initial={'tank': 123})
If you want to add files or images also
form = JournalForm(request.POST or None,request.FILES or None,initial={'tank': 123})
I hope this can help you:
form.instance.updatedby = form.cleaned_data['updatedby'] = request.user.id
I also encountered the need to set default values in the form during development. My solution is
initial={"":""}
form=ArticleModel(request.POST)
if form.has_changed():
data = {i: form.cleaned_data[i] for i in form.changed_data}
data.update({key: val for key, val in init_praram.items() if key not in form.changed_data})
use form.has_changed ,if form.fields is required you can use this method
How I added the initial to the form:
I read #Sergey Golovchenko answer.
So I just added it to the form in if request.method == 'POST':.
But that's not where you place it, if you want to see what value it got before posting the form.
You need to put it in the form where the else is.
Example here from views.py
def myForm(request):
kontext = {}
if request.method == 'POST':
# You might want to use clean_data instead of initial here. I found something on a stack overflow question, and you add clean data to the Forms.py, if you want to change the post data. https://stackoverflow.com/questions/36711229/django-forms-clean-data
form = myModelForm(request.POST, initial={'user': request.user})
if form.is_valid():
form.save()
return redirect('/')
else:
# you need to put initial here, if you want to see the value before you post it
form = myModelForm(initial={'user': request.user})
kontext['form'] = form
return render(request, 'app1/my_form.html', kontext)

ModelForm saving over model data with empty fields

I'm building an Edit form for a model in my database using a ModelForm in Django. Each field in the form is optional as the user may want to only edit one field.
The problem I am having is that when I call save() in the view, any empty fields are being saved over the instance's original values (e.g. if I only enter a new first_name, the last_name and ecf_code fields will save an empty string in the corresponding instance.)
The form:
class EditPlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = ['first_name', 'last_name', 'ecf_code']
def __init__(self, *args, **kwargs):
super(EditPlayerForm, self).__init__(*args, **kwargs)
self.fields['first_name'].required = False
self.fields['last_name'].required = False
self.fields['ecf_code'].required = False
The view:
def view(request, player_pk = ''):
edit_player_form = forms.EditPlayerForm(auto_id="edit_%s")
if "edit_player_form" in request.POST:
if not player_pk:
messages.error(request, "No player pk given.")
else:
try:
selected_player = Player.objects.get(pk = player_pk)
except Player.DoesNotExist:
messages.error(request, "The selected player could not be found in the database.")
return redirect("players:management")
else:
edit_player_form = forms.EditPlayerForm(
request.POST,
instance = selected_player
)
if edit_player_form.is_valid():
player = edit_player_form.save()
messages.success(request, "The changes were made successfully.")
return redirect("players:management")
else:
form_errors.convert_form_errors_to_messages(edit_player_form, request)
return render(
request,
"players/playerManagement.html",
{
"edit_player_form": edit_player_form,
"players": Player.objects.all(),
}
)
I've tried overriding the save() method of the form to explicitly check which fields have values in the POST request but that didn't seem to make any difference either.
Attempt at overriding the save method:
def save(self, commit = True):
# Tried this way to get instance as well
# instance = super(EditPlayerForm, self).save(commit = False)
self.cleaned_data = dict([ (k,v) for k,v in self.cleaned_data.items() if v != "" ])
try:
self.instance.first_name = self.cleaned_data["first_name"]
except KeyError:
pass
try:
self.instance.last_name = self.cleaned_data["last_name"]
except KeyError:
pass
try:
self.instance.ecf_code = self.cleaned_data["ecf_code"]
except KeyError:
pass
if commit:
self.instance.save()
return self.instance
I also do not have any default values for the Player model as the docs say the ModeForm will use these for values absent in the form submission.
EDIT:
Here is the whole EditPlayerForm:
class EditPlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = ['first_name', 'last_name', 'ecf_code']
def __init__(self, *args, **kwargs):
super(EditPlayerForm, self).__init__(*args, **kwargs)
self.fields['first_name'].required = False
self.fields['last_name'].required = False
self.fields['ecf_code'].required = False
def save(self, commit = True):
# If I print instance variables here they've already
# been updated with the form values
self.cleaned_data = [ k for k,v in self.cleaned_data.items() if v ]
self.instance.save(update_fields = self.cleaned_data)
if commit:
self.instance.save()
return self.instance
EDIT:
Ok so here is the solution, I figured I'd put it here as it might be useful to other people (I've certainly learned a bit from this).
So it turns out that the is_valid() method of the model form actually makes the changes to the instance you pass into the form, ready for the save() method to save them. So in order to fix this problem, I extended the clean() method of the form:
def clean(self):
if not self.cleaned_data.get("first_name"):
self.cleaned_data["first_name"] = self.instance.first_name
if not self.cleaned_data.get("last_name"):
self.cleaned_data["last_name"] = self.instance.last_name
if not self.cleaned_data.get("ecf_code"):
self.cleaned_data["ecf_code"] = self.instance.ecf_code
This basically just checks to see if the fields are empty and if a field is empty, fill it with the existing value from the given instance. clean() gets called before the instance variables are set with the new form values, so this way, any empty fields were actually filled with the corresponding existing instance data.
You could maybe use the update() method instead of save()
or the argument update_field
self.instance.save(update_fields=['fields_to_update'])
by building the list ['fields_to_update'] only with the not empty values.
It should even work with the comprehension you've tried :
self.cleaned_data = [ k for k,v in self.cleaned_data.items() if v ]
self.instance.save(update_fields=self.cleaned_data)
EDIT :
Without overriding the save method (and commenting out this attempt in the form):
not_empty_data = [ k for k,v in edit_player_form.cleaned_data.items() if v ]
print(not_empty_data)
player = edit_player_form.save(update_fields=not_empty_data)
You could check the values if it's not empty in your view without overriding save()
if edit_player_form.is_valid():
if edit_player_form.cleaned_data["first_name"]:
selected_player.first_name = edit_player_form.cleaned_data["first_name"]
if edit_player_form.cleaned_data["last_name"]:
selected_player.last_name= edit_player_form.cleaned_data["last_name"]
if edit_player_form.cleaned_data["ecf_code"]:
selected_player.ecf_code= edit_player_form.cleaned_data["ecf_code"]
selected_player.save()
This should work fine with what you want. I'm not sure if it's the best way to do it but it should work fine.

Django forms initial value user_full_name

Is the any solution to get django's user_full_name as a initial value for form? My idea was to display a django's form on the end of shopping to finish a order. I want also do put into a form total value, but this is for later.
I did something like this:
user_dane = request.user.get_full_name
koszyk = request.session.get('koszyk', [])
produkty = list(Produkt.objects.filter(pk__in=koszyk))
suma_cen = Produkt.objects.filter(pk__in=koszyk).aggregate(suma=Sum('cena'))
suma_wszystkich_cen = suma_cen['suma']
form=ZamowienieForm(initial={'imie_nazwisko':user_dane, 'kwota_do_zaplaty':suma_wszystkich_cen})
but this is working only when request.method is POST.
if request.method =='POST':
form = ZamowienieForm()
According to documentation I shouldn't initial a empty form with POST... Is there any chance to have a user full name into a form?
Here is the form class:
class ZamowienieForm(forms.ModelForm):
class Meta:
model = Zamowienie
fields = ('imie_nazwisko', 'kwota_do_zaplaty', 'miejscowosc',
'ulica','numer_domu', 'numer_mi‌​eszkania', 'kod_pocztowy',)
class NewMeta:
readonly = ('imie_nazwisko','kwota_do_zaplaty',)
Maybe try something like this inside ZamowienieForm class
def __init__(self, *args, **kwargs):
super(ZamowienieForm, self).__init__(*args, **kwargs)
self.fields['imie_nazwisko'] = self.initial.get('imie_nazwisko')
self.fields['kwota_do_zaplaty'] = self.initial.get('kwota_do_zaplaty')
Although I don't understand why "initial" is not working out of the box
In this case, you only need to initialize your form once, and not inside a conditional check if the request is a GET or POST:
def your_view(request):
form = ZamowienieForm(
request.POST or None,
initial={'imie_nazwisko': request.user.get_full_name(),
'kwota_do_zaplaty': suma_wszystkich_cen}
)
if request.method == 'POST' and form.is_valid():
# do whatever
This way you are always passing in the initial value, and if request.method == 'GET', then None is passed as the first positional argument to the form.
Also, user.get_full_name is an instance method, not a property, so using request.user.get_full_name only returns the bound method, not the actual value. You have have to call the function using ()
Finally, this will only work for users that are authenticated. The anonymous user object in Django won't return any user-specific information.

Django Form Clean Method Test Error

I'm testing a form that I wrote up earlier. For some reason, the test won't pass. It's like the form is ignoring the data I pass it, and I don't see why. The traceback tells me that the user variable in the clean method of the form is None, though a User is definitely passed into the form. The traceback:
... in clean
if user.pk is not userwebsite.user.pk: AttributeError: 'NoneType' object has no attribute 'pk'
The Form:
class CreateAuditForm(forms.Form):
user = forms.ModelChoiceField(queryset=User.objects.all(), widget=HiddenInput)
website = forms.ModelChoiceField(queryset=UserWebsite.objects.all(), widget=HiddenInput)
emails = forms.CharField(
max_length=250,
required=False
)
def clean_user(self):
user = self.cleaned_data.get('user', None)
if not user.groups.filter(name__iexact='subscribed').exists() and not user.groups.filter(name__iexact='addon').exists():
raise forms.ValidationError(_("You must have an active subscription to request \
website audits. Please try again after subscribing to us."))
return user
def clean(self):
data = self.cleaned_data
user = data.get('user')
userwebsite = data.get('website', None)
if userwebsite.user:
if user.pk is not userwebsite.user.pk:
raise forms.ValidationError(_("Sorry, try again."))
elif userwebsite.addon:
if user.pk is not userwebsite.addon.pk:
raise forms.ValidationError(_("Sorry, try again."))
return self.cleaned_data
def save(self):
# Action
The Test:
class CreateAuditFormTestCase(TestCase):
def setUp(self):
super(CreateAuditFormTestCase, self).setUp()
self.form = CreateAuditForm
...
self.website = Website.objects.create(
title="permanence",
url="https://www.badabuyhere.com",
display="www.bababuyhere.com")
self.unsubscriber = User.objects.create(
username="adiagojesse",
first_name="adiago",
last_name="jesse",
email="bannerfare#coldmount.com",
password="tigermountainvalley"
)
self.unsubscriberwebsite = UserWebsite.objects.create(
user=self.unsubscriber,
website=self.website,
is_competitor=False
)
...
def test_user_validation(self):
data = {
"user":self.unsubscriber.pk,
"website":self.unsubscriberwebsite.pk,
"emails":"john#gmail.com, jeff#gmail.com"
}
self.assertTrue(self.unsubscriber)
self.assertTrue(self.unsubscriberwebsite)
audit = self.form(data)
self.assertEqual(audit.is_valid(), False)
This is probably a simple issue that I can't pick up on, which is what's frustrating me, lol. Help would be appreciated.
My guess is that in CreateAuditForm.clean, useris None because clean_user raised a ValidationError. The ValidationError comes from the fact that the user does not have the groups he needs.
Another issue I see is that to test equality between model instances in Django, you should not use primary keys but test using the instances directly, using == and not is. (See https://stackoverflow.com/a/13650309/1644198 for more information on is and ==)
Example:
if user != userwebsite.user:
# etc...

Categories

Resources