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)
Related
I have a survey app - you create a Survey and it saves the Response. It's registered in Django Admin. I can see the Survey and submit a Response. When I click Response in Admin, I get the following error:
ValueError at /admin/django_survey/response/
Cannot query "response 5f895af5999c49929a522316a5108aa0": Must be "User" instance.
So I checked the SQL database and for django_survey_response I can see that there is a response, but the column user_id is NULL.
I suspected that there's an issue with my Views and/or Forms and I'm not saving the logged in User's details, so I've tried to address that.
However, now I get
NameError at /survey/1/
global name 'user' is not defined
How do I resolve this? I want the form to save Response with the logged in user's ID.
The Traceback:
django_survey\views.py
def SurveyDetail(request, id):
survey = Survey.objects.get(id=id)
category_items = Category.objects.filter(survey=survey)
categories = [c.name for c in category_items]
print 'categories for this survey:'
print categories
if request.method == 'POST':
form = ResponseForm(request.POST, survey=survey) <.........................
if form.is_valid():
response = form.save()
return HttpResponseRedirect("/confirm/%s" % response.interview_uuid)
else:
form = ResponseForm(survey=survey)
print form
django_survey\forms.py
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
self.survey = survey
self.user = user <.........................
super(ResponseForm, self).__init__(*args, **kwargs)
self.uuid = random_uuid = uuid.uuid4().hex
# add a field for each survey question, corresponding to the question
# type as appropriate.
data = kwargs.get('data')
It might be worth noting that previously, instead of user, the model's field was called interviewee. I changed this and ran migrations again.
I am also using userena.
The error message in this instance is python trying to tell you that you are attempting to access a variable user that has not been defined in the scope of your method.
Let's look at the first few lines of the __init__() method:
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
self.survey = survey
self.user = user
We can see where the survey variable is defined: survey = kwargs.pop('survey'). It is passed into the form as a keyword argument and extracted in the forms __init__. However underneath you attempt to do the same thing with user but haven't actually defined it above. The correct code would look like:
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
user = kwargs.pop('user')
self.survey = survey
self.user = user
However, this still won't work because we aren't passing the user variable to the form via kwargs. To do that we pass it in when we initialise the form in your views.py. What isn't clear is what user object you are expecting to pass in. the request.user? or does the Survey object have a user attribute? in which case you would not need to pass user in and would just use survey.user etc.
django_survey\views.py
def SurveyDetail(request, id):
survey = Survey.objects.get(id=id)
category_items = Category.objects.filter(survey=survey)
categories = [c.name for c in category_items]
print 'categories for this survey:'
print categories
if request.method == 'POST':
form = ResponseForm(request.POST, survey=survey, user=request.user)
if form.is_valid():
response = form.save()
return HttpResponseRedirect("/confirm/%s" % response.interview_uuid)
else:
form = ResponseForm(survey=survey, user=request.user)
print form
In your view when you initialize your form you need to pass it the user (current user in this case)? similar to this form = ResponseForm(request.POST, survey=survey, user=request.user). Then in the __init__ of your form pop the user object user = kwargs.pop('user'). I believe that will resolve your issue.
I want to offer users the possibility to create a new publication based on an existing publication. To do that, I want them to click a link to "basedview" that contains the id of the publication they want the new item to base on. There are two formsets for n:n relations included.
that should open a prefilled form with all fields prefield with the data from the publication it's based on. once the user has made changes as needed, it should then save a new publication and new relations for the fieldset - the latter being the difficult part of it.
So my question is - how can I load all corresponding formsets from the database and then delete all their pk but still keep the relation to the publication item?
Right now it is like this in the get method:
self.object = None
try:
self.object = KombiPublikation.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404("Keinen Output unter dieser PubID gefunden.")
form = KombiPublikationForm(instance=self.object)
pubspr_formset = KombiPublikationSpracheFormset(instance=self.object)
pubpers_formset = KombiPublikationPersonFormset(instance=self.object)
But that ends up to be just an edit of the existing publication. I somehow have to delete the pk after I populated the formset or find a way to populate the formset differently. Any Ideas?
Thank you very much!
Here the full code excerpt:
class PublikationBasedView(PublikationCreateView):
def get(self, request, *args, **kwargs):
self.object = None
try:
self.object = KombiPublikation.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404("Keinen Output unter dieser PubID gefunden.")
#todo: delete the pk of all objects in forms in formset, else they stay the same and are also changed!!
#fix: delete pk in objekt in order to save it as a new objekt - else based does not work at all!
#self.object.pk=None
form = KombiPublikationForm(instance=self.object)
pubspr_formset = KombiPublikationSpracheFormset(instance=self.object)
pubpers_formset = KombiPublikationPersonFormset(instance=self.object)
return self.render_to_response(
self.get_context_data(
form=form,
pubspr_formset=pubspr_formset,
pubpers_formset=pubpers_formset,
)
)
#its based on this create view
class PublikationCreateView(LoginRequiredMixin, ShowNumberOfItems, CreateView):
form_class = KombiPublikationForm
template_name = 'output/pub_create.html'
model = KombiPublikation
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
pubspr_formset = KombiPublikationSpracheFormset()
pubpers_formset = KombiPublikationPersonFormset()
return self.render_to_response(
self.get_context_data(
form=form,
pubspr_formset=pubspr_formset,
pubpers_formset=pubpers_formset
)
)
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
pubspr_formset = KombiPublikationSpracheFormset(self.request.POST)
pubpers_formset = KombiPublikationPersonFormset(self.request.POST)
if form.is_valid() and pubspr_formset.is_valid() and pubpers_formset.is_valid():
return self.form_valid(form, pubspr_formset, pubpers_formset)
else:
return self.form_invalid(form, pubspr_formset, pubpers_formset)
def get_success_msg(self):
return 'Ihr Output wurde erfolgreich unter PubID {} angelegt. Speicherort: {}. <br>'.format(self.object.pk, self.object.status)
def form_valid(self, form, pubspr_formset, pubpers_formset):
""" Called if all forms are valid."""
self.object = form.save()
pubspr_formset.instance = self.object
pubspr_formset.save()
pubpers_formset.instance = self.object
pubpers_formset.save()
messages.success(self.request, self.get_success_msg())
return redirect(self.get_success_url())
def form_invalid(self, form, pubspr_formset, pubpers_formset):
""" Called if whether a form is invalid. Re-renders data-filled forms and errors."""
return self.render_to_response(
self.get_context_data(
form=form,
pubspr_formset=pubspr_formset,
pubpers_formset=pubpers_formset,
))
Once you've set the Form's instance it's bound to that object. All updates will be to the object you passed.
Instead you need to set the Form's initial value
Use initial to declare the initial value of form fields at runtime. For example, you might want to fill in a username field with the username of the current session.
Then there's a utility at django.forms.models.model_to_dict that'll give you the dict you need for initial:
Returns a dict containing the data in instance suitable for passing as a Form's initial keyword argument.
So you'll need to do something like this:
from django.forms.models import model_to_dict
object = # Your code here...
# You don't want `id`. Possibly others...?
initial_data = model_to_dict(object, exclude=['id'])
form = YourFormClass(initial=initial_data)
# ...
Hopefully that helps.
I solved the problem and since it was a bit more complicated then expected, I share my finding here - if someone finds a simpler solution feel free to add another comment
That is the final get method in the view:
def get(self, request, *args, **kwargs):
self.object = None
try:
self.object = KombiPublikation.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404("Keinen Output unter dieser PubID gefunden.")
#load all form initials and render the form correctly - but save new objects
#1. make sure the main publikation object is saved as a new object:
self.object.pk = None
self.object.erstellungsdatum = datetime.now()
form = KombiPublikationForm(instance=self.object)
#2. get the corresponding querysets for sprache and person:
pubspr = KombiPublikationSprache.objects.filter(publikation=self.kwargs['pk'])
pubpers = KombiPublikationPerson.objects.filter(publikation=self.kwargs['pk'])
#make a list of dicts out of the querysets and delete pk id and fk relations
pubspr_listofdicts = []
for pubspr in pubspr:
pubspr_dict= model_to_dict(pubspr)
del pubspr_dict['id']
del pubspr_dict['publikation']
pubspr_listofdicts.append(pubspr_dict)
pubpers_listofdicts = []
for pubpers in pubpers:
pubpers_dict=model_to_dict(pubpers)
del pubpers_dict['id']
del pubpers_dict['publikation']
pubpers_listofdicts.append(pubpers_dict)
#create new formsets with the right amount of forms (leng(obj_listofdicts)
KombiPublikationSpracheFormset = inlineformset_factory(KombiPublikation,
KombiPublikationSprache,
form=KombiPublikationSpracheForm,
extra=len(pubspr_listofdicts),
can_delete=True,
can_order=True,
min_num=1,
validate_min=True)
KombiPublikationPersonFormset = inlineformset_factory(
KombiPublikation,
KombiPublikationPerson,
form=KombiPublikationPersonForm,
extra=len(pubpers_listofdicts),
can_delete=True,
can_order=True,
min_num=0,
validate_min=True)
#initiate the formset with initial data:
pubspr_formset = KombiPublikationSpracheFormset(instance=self.object, initial=pubspr_listofdicts)
pubpers_formset = KombiPublikationPersonFormset(instance=self.object, initial=pubpers_listofdicts)
return self.render_to_response(
self.get_context_data(
form=form,
pubspr_formset=pubspr_formset,
pubpers_formset=pubpers_formset,
)
)
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
I have the following models:
class Project(models.Model):
title = models.CharField(max_length="100")
pub_date = models.DateField(auto_now_add=True, editable=False)
budget = models.IntegerField()
class Milestone(models.Model):
title = models.CharField(max_length="50")
budget_percentage = models.IntegerField(max_length=2)
project = models.ForeignKey(Project)
In the creation form for a Project, i've included an inline formset for milestones.
I want to validate that when a Project is subbmitted, at least 4 milestones are created, also that the budget_percentage of all milestones sums up to 100
This is my form:
class BaseMilestoneProjectFormSet(BaseFormSet):
def clean(self):
if any(self.errors):
# Don't bother validating the forms unless each form is valid on its own
return
if len(self.forms) < REQUIRED_MILESTONES:
raise forms.ValidationError("At least %s milestones need to be created" % REQUIRED_MILESTONES)
# Set initial control variables
# Total percentage of budget to control
total_percentage = 0
# Date to control that milestones are linear, i.e. that second milestone isn't delivered before first
current_control_date = date.min
for i, form in zip(range(len(self.forms)), self.forms):
if i == 0 and form.budget_percentage > MAX_BUDGET_FIRST_MILESTONE:
raise forms.ValidationError("First milestone budget must not exceed %s percentage" % MAX_BUDGET_FIRST_MILESTONE)
elif form.budget_percentage > MAX_BUDGET_MILESTONE:
raise forms.ValidationError("Milestone's budget must not exceed %s percentage" % MAX_BUDGET_MILESTONE)
if form.estimated_delivery_date < current_control_date:
raise forms.ValidationError("Milestones must be linear, check your delivery dates")
# Set control variables for next iteration
current_control_date = form.estimated_delivery_date
total_percentage += form.budget_percentage
if total_percentage != 100:
raise forms.ValidationError("All milestones budget percentage should sum up to 100%")
When I submit the form with 3 empty milestones forms it doesn't do nothing about the first
forms.ValidationError(...)
I've verified with raise Exception() that in fact it enters the if, but it continues as if nothing happens.
It's an error in my code or a misunderstood concept of ValidationError?
Any help would be greatly appreciated, thanks in advance.
Edit:
I'm adding the view code that was missing
class DelayedModelFormMixin(ModelFormMixin):
def form_valid(self, form):
self.object = form.save(commit=False)
self.prepare_object_for_save(self.object)
self.object.save()
if hasattr(self.object, "save_m2m"):
self.object.save_m2m()
return super(ModelFormMixin, self).form_valid(form)
def prepare_object_for_save(self, obj):
pass
class ProjectNew(CreateView, DelayedModelFormMixin):
model = Project
success_url = '/projects/project/%(slug)s'
form_class = ProjectForm
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(ProjectNew, self).dispatch(request, *args, **kwargs)
def prepare_object_for_save(self, obj):
obj.owner = self.request.user
# Code for stacked milestones and rewards
context = self.get_context_data()
milestone_form = context['milestone_formset']
reward_form = context['reward_formset']
if milestone_form.is_valid() and reward_form.is_valid():
self.object = form.save()
milestone_form.instance = self.object
milestone_form.save()
reward_form.instance = self.object
reward_form.save()
return HttpResponseRedirect(success_url)
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form))
def get_context_data(self, **kwargs):
context = super(ProjectNew, self).get_context_data(**kwargs)
if self.request.POST:
context['milestone_formset'] = MilestoneFormSet(self.request.POST)
context['reward_formset'] = RewardFormSet(self.request.POST)
else:
context['milestone_formset'] = MilestoneFormSet()
context['reward_formset'] = RewardFormSet()
return context
If i understand your question the problem is that you are counting how many forms are inside the formset, but you want/need to count how many BOUND forms are in the formset.
So, i think you need to change this line:
if len(self.forms) < REQUIRED_MILESTONES:
for these lines:
bounded_forms = filter(lambda form:form.isbound(), self.forms)
if len(bounded_forms) < REQUIRED_MILESTONES:
then you can raise the validation error as you are doing it.
Edit:
Maybe you are not understanding how formset validation works. May be this can help, specially this phrase:
The formset clean method is called after all the Form.clean methods have been called. The errors will be found using the non_form_errors() method on the formset.
If i understand what you mean when you say
When I submit the form with 3 empty milestones forms it doesn't do nothing about the first forms.ValidationError(...)
then, you are not seeing the raised errors. So, the place to look for them is the formset "non_form_errors" method. Put something like this in your template:
<span>{{milestone_formset.non_form_errors}}</span>
Now you should see the errors you are raising in milestone formset clean method.
Hope it helps!
I have the following form with dynamic fields:
1) In models.py I want to pass in a value to the form to query
class InsertValuesForm(forms.Form):
def __init__(self, idfield, *args, **kwargs):
super(InsertValuesForm, self).__init__(*args, **kwargs)
for f in Parameter.objects.filter(id=idfield):
if Part.objects.get(parameter=f.parameter_name).isfunction:
self.fields.update({
f.parameter_name) : forms.CharField(widget=forms.TextInput() )})
1) In views.py
def something(request)
#......
idfield = request.GET.get('feid','0')
form = InsertValuesForm(request.POST,idfield)
if request.method == 'POST':
if form.is_valid():
#some code
else:
form = InsertValuesForm(idfield)
return render_to_response('fuzz/configuration.html', {
'form': form,
},context_instance=RequestContext(request))
In number 1 situation, I was able to display the dynamic fields in the for loop. However, the form threw me this error after filling up all of the fields and submitting(POST):
int() argument must be a string or a number, not 'QueryDict'. I was thinking it is the request.POST that is causing the error.
after doing some research on this problem, there were similar solutions like this:
http://groups.google.com/group/django-users/browse_thread/thread/ddefd76324ffe6cd
http://groups.google.com/group/django-users/browse_thread/thread/495a917396b20b37/c430d71a31204e5d#c430d71a31204e5d
2) In models.py
class InsertValuesForm(forms.Form):
def __init__(self, *args, **kwargs):
idfield = kwargs.pop('idfield', False)
super(InsertValuesForm, self).__init__(*args, **kwargs)
for f in Parameter.objects.filter(id=idfield):
if Part.objects.get(parameter=f.parameter_name).isfunction:
self.fields.update({
f.parameter_name) : forms.CharField(widget=forms.TextInput() )})
and
the following snippet of views.py(same as number 1)
def something(request)
#......
idfield = request.GET.get('feid','0')
form = InsertValuesForm(request.POST,idfield)
if request.method == 'POST':
if form.is_valid():
#some code
else:
form = InsertValuesForm(idfield)
return render_to_response('fuzz/configuration.html', {
'form': form,
},context_instance=RequestContext(request))
Now the above code doesn't even display the dynamic textboxes and is just displayed as blank page. Appreciate if anybody can shed some light on
how to display these dynamic textboxes and at the same time,
getting the values of these textboxes via request.POST and also making the form validate. Thanks
You have changed the signature of the form's __init__ so that the first positional parameter is idfield. Don't do that, because now when you instantiate it with form = InsertValuesForm(request.POST,idfield), it's taking the first parameter to be idfield, rather than request.POST.
Instead, define the method like this:
def __init__(self, *args, **kwargs):
idfield = kwargs.pop('idfield', None)
...etc...
and instantiate it with a keyword arg:
form = InsertValuesForm(request.POST, idfield=idfield)