I am using a django forms SessionWizardView to implement a form wizard. The url configuration is as follows;
url(r'^contact/(?P<pk>\d+)/$', views.ContactWizard.as_view([DummyForm, OtherForm]), name='contact'),
The idea here is that I am passing a primary key so that I can populate the form with some initial data. The form is loaded from another view as follows:
def populate_review_form(request):
pk = 2
return redirect('contact', pk=pk)
Here I am trying to pass a hard-coded primary key to the form.
Now, my form implementation is simple where I am overriding the get_form_initial to generate the initial value for the form as follows:
class ContactWizard(SessionWizardView):
def get_template_names(self):
return "test.html"
def get_form_initial(self, step):
pk = self.request.GET.get('pk')
# Just attempt to print the primary key
print pk, self.request.GET
return {}
def done(self, form_list, **kwargs):
return HttpResponseRedirect('index')
The issue is that this always prints None <QueryDict: {}>. So, basically my parameter is not being passed or I am unable to access the correct request object. Is it possible in Django to have this communication?
pk is stored in self.kwargs:
pk = self.kwargs['pk']
Related
I want to display initial values as selected on a MultipleChoice form field in Django when the form loads. I populate a formset with different forms. Each form has only one field 'answer', which is initialized based on a custom parameter passed to the form's init() method.
class AnswerForm(forms.Form):
def __init__(self, *args, **kwargs):
"""
Initialize label & field
:returns None:
"""
question = kwargs.pop('question') # A Question object
super(AnswerForm, self).__init__(*args, **kwargs)
if question.type == Types.RADIO:
choices_ = [(op.id, op) for op in question.option_set.all()]
self.fields['answer'] = forms.ChoiceField(label=question.statement,
initial=1,
widget=forms.RadioSelect,
choices=choices_)
elif question.type == Types.CHECKBOX:
choices_ = [(op.id, op) for op in question.option_set.all()]
self.fields['answer'] = forms.MultipleChoiceField(label=question.statement,
initial=[1,3],
widget=forms.CheckboxSelectMultiple,
choices=choices_)
This renders the following HTML:
But it doesn't get into the form's cleaned_data. When I submit formset, the request.POST data goes into this view:
def post(self, request, form_id):
"""
Process & save the responses obtained from a form into DB
:param request: An HTTPRequest object
:param form_id: form id whose responses arrive
:returns HttpResponse object with a results template
"""
formset = FormHandler.AnswerFormSet(request.POST, request.FILES,
form_kwargs={'questions': FormHandler.qs})
if formset.is_valid():
for form in formset:
cd = form.cleaned_data
# Access cd['answer'] here but cd appears to be empty dict {}
# with no key named 'answer'
The cleaned_data does have the correct 'answer' value in the case of Radio, but in this case, it doesn't contain the list of selected IDs which it should. I've checked that request.POST.getlist('form_#_answer') does show the correct list of ['1', '3'] but it somehow doesn't get into the formset's cleaned_data. I've spent hours trying to find out why this happens. Can't find the answer anywhere in the Django docs either. Can anyone explain why this is happening?
I have made a form to filter ListView
class SingleNewsView(ListView):
model = News
form_class = SearchForm
template_name = "single_news.html"
def get(self, request, pk, **kwargs):
self.pk = pk
pub_from = request.GET['pub_date_from']
pub_to = request.GET['pub_date_to']
return super(SingleNewsView,self).get(request,pk, **kwargs)
My form fields are pub_date_from and pub_date_to. When I run the site it says:
MultiValueDictKeyError .
I don't know what's going on. When I remove the two line of getting pub_from and pub_to the site works fine. I want these two values to filter the queryset.
On first request there is no form data submitted so request.GET would not have any data. So doing request.GET['pub_date_from'] will fail. You shall use .get() method
pub_from = request.GET.get('pub_date_from')
pub_to = request.GET.get('pub_date_to')
If these keys are not in the dict, will return None. So handle the such cases appropriately in your code.
Also, if you want to filter objects for ListView add get_queryset() method to return filtered queryset as explained here Dynamic filtering
I've got an activation url that carries the activation key ( /user/activate/123123123 ). That works without any issue. get_context_data can plop it into the template fine. What I want to do is have it as an initial value for the key field so the user only needs to enter username and password as created at registration.
How can I pull the key from context or get() without hardcoding the field into the template?
class ActivateView(FormView):
template_name = 'activate.html'
form_class = ActivationForm
#initial={'key': '123123123'} <-- this works, but is useless
success_url = 'profile'
def get_context_data(self, **kwargs):
if 'activation_key' in self.kwargs:
context = super(ActivateView, self).get_context_data(**kwargs)
context['activation_key'] = self.kwargs['activation_key']
"""
This is what I would expect to set the value for me. But it doesn't seem to work.
The above context works fine, but then I would have to manually build the
form in the template which is very unDjango.
"""
self.initial['key'] = self.kwargs['activation_key']
return context
else:
return super(ActivateView, self).get_context_data(**kwargs)
You can override get_initial to provide dynamic initial arguments:
class ActivationView(FormView):
# ...
def get_initial(self):
return {'key': self.kwargs['activation_key']}
I have a form which looks like this:
class AddressSearchForm(forms.Form):
"""
A form that allows a user to enter an address to be geocoded
"""
address = forms.CharField()
I'm not storing this value, however, I am geocoding the address and checking to make sure it is valid:
def clean_address(self):
address = self.cleaned_data["address"]
return geocode_address(address, True)
The geocode function looks like this:
def geocode_address(address, return_text = False):
""" returns GeoDjango Point object for given address
if return_text is true, it'll return a dictionary: {text, coord}
otherwise it returns {coord}
"""
g = geocoders.Google()
try:
#TODO: not really replace, geocode should use unicode strings
address = address.encode('ascii', 'replace')
text, (lat,lon) = g.geocode(address)
point = Point(lon,lat)
except (GQueryError):
raise forms.ValidationError('Please enter a valid address')
except (GeocoderResultError, GBadKeyError, GTooManyQueriesError):
raise forms.ValidationError('There was an error geocoding your address. Please try again')
except:
raise forms.ValidationError('An unknown error occured. Please try again')
if return_text:
address = {'text':text, 'coord':point}
else:
address = {'coord':point}
return address
What I now need to do is create a view that will query a model using the address data to filter the results. I'm having trouble figuring out how to do this. I'd like to use CBV's if possible. I can use FormView to display the form, and ListView to display the query results, but how do I pass the form data between the two?
Thanks in advance.
UPDATE: I know how to query my model to filter the results. I just don't know how to properly combine using a Form and Class Based Views so that I can access the cleaned_data for my filter. e.g:
Process should be:
1) Display form on get
2) Submit form and validate (geocode address) on post
3) Run query and display results
address = form.cleaned_data['address']
point = address['coord']
qs = model.objects.filter(point__distance_lte=(point, distance)
Ok, here's a generic version of what ended up working based on psjinx direction:
from django.views.generic.base import TemplateResponseMixin, View
from django.views.generic.edit import FormMixin
from django.views.generic.list import MultipleObjectMixin
class SearchView(FormMixin, MultipleObjectMixin, TemplateResponseMixin, View):
"""
A View which takes a queryset and filters it via a validated form submission
"""
queryset = {{ initial queryset }} # you can use a model here too eg model=foo
form_class = {{ form }}
def get(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
queryset = self.get_queryset()
search_param = form.cleaned_data['{{ form field }}']
object_list = queryset.filter({{ filter operation }}=search_param)
context = self.get_context_data(object_list=object_list, form=form, search_param=search_param)
return self.render_to_response(context)
This is similar to a question asked here or I will say a combination of two questions.
Django: Search form in Class Based ListView
Search multiple fields of django model without 3rd party app
django simple approach to multi-field search (if your models similar to one mentioned in this question)
Please have a look at above questions and their answers and if you still have any question then reply in comment to this answer.
Update 1
from django.views.generic.base import TemplateResponseMixin, View
from django.views.generic.edit import FormMixin
from django.views.generic.list import MultipleObjectMixin
class SearchView(FormMixin, MultipleObjectMixin, TemplateResponseMixin, View):
model = SomeModel
form_class = AddressSearchForm
template = "search.html"
def get_queryset():
## Override it here
pass
def post():
## If your form is invalid then request won't reach here
## because FormMixin is in left most position
## do something
## call self.render_to_response()
def form_valid():
## Your form is valid do something
## if form is invalid then next method will be called
pass
def form_invalid(self):
## form is not valid
## render to some template
Useful Links:
https://github.com/django/django/blob/1.4.3/django/views/generic/base.py
https://github.com/django/django/blob/1.4.3/django/views/generic/edit.py
https://github.com/django/django/blob/1.4.3/django/views/generic/list.py
Related Question:
Django - Mixing ListView and CreateView
I'm still getting to grips with Django and, in particular, Forms.
I created MyForm which subclasses forms.Form in which I define a field like this :
owner = forms.CharField(widget=forms.HiddenInput)
When I create a new, blank instance of the Form I want to prefill this with the creator's profile, which I'm doing like this :
form = MyForm( {'owner' : request.user.get_profile()} )
Which I imagine sets the owner field of the form to the request.user's id. (The type of the corresponding "owner" field in the models.Model class is ForeignKey of Profile.)
Before rendering the form, I need to check one piece of information about the owner. So I try to access form.owner, but there seems to be no "owner" attribute of the form object. I also tried form.instance.owner, but similarly, no luck.
What am I doing wrong? What have I misunderstood?
You can access this value via the form's data dictionary:
form.data.get('owner')
Initial data in a form should be passed in with the initial kwarg.
Once you've turned the form into a bound form (usually by passing request.POST in as the first argument to instantiate the form, the place you are currently incorrectly providing the initial dictionary), and performed validation with form.is_valid(), the data the user submitted will be in form.cleaned_data, a dictionary. If they changed the initial value, their changed value will be in that dictionary. If not, your initial value will be.
If you don't want to let the user modify the value, then don't have it be a field, instead pass it in as a kwarg, and store it as an instance attribute in form.__init__():
class MyForm(Form):
def __init__(self, *args, profile, **kwargs):
super().__init__(*args, **kwargs)
self.profile = profile
...
form = MyForm(
request.POST if request.POST else None,
profile=request.user.get_profile(),
)
if request.method == "POST" and form.is_valid():
do_stuff_with(form.profile)
Also as with most things, this all gets easier if you drink the Django kool-aid and use generic views.
class MyView(FormView):
form_class = MyForm
...
def get_form_kwargs(self):
return {
**super().get_form_kwargs(),
"profile": self.request.user.get_profile()
}
def form_valid(self, form):
do_stuff_with(form.profile)
return super().form_valid(form)
Or for the initial case whereby you want it to be editable:
class MyView(FormView):
form_class = MyForm
...
def get_initial(self):
return {
**super().get_initial(),
"profile": self.request.user.get_profile()
}
def form_valid(self, form):
do_stuff_with(form.cleaned_data.get("profile"))
return super().form_valid(form)
If MyForm happens to be a form about one single instance of a specific model then this gets even easier with UpdateView instead of FormView. The more you buy into the way Django wants to do things, the less you have to work.