I was looking at the documentation and I am not quite sure how to use different templates for each step...
I looked into the source code and it seems that the template name is hardcoded:
class WizardView(TemplateView):
"""
The WizardView is used to create multi-page forms and handles all the
storage and validation stuff. The wizard is based on Django's generic
class based views.
"""
storage_name = None
form_list = None
initial_dict = None
instance_dict = None
condition_dict = None
template_name = 'formtools/wizard/wizard_form.html'
...........
The docs say something about mixins, but I am not sure how to use them since I just started with django...
Thanks
UPDATE:
I looked further into the source code and realized that there is a method get_template_names.
I tried:
class AddWizard(SessionWizardView):
def get_template_names(self, step):
if step == 0:
return 'business/add1.html'
return 'business/add2.html'
def done(self, form_list, **kwargs):
return render_to_response('business/done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
But got an error:
get_template_names() takes exactly 2 arguments (1 given)
get_template_names doesn't accept arguments. You can't just define a new argument for a function to accept and hope the framework will pass it in! (for your future troubleshooting)
Judging by the WizardView source, it looks like you can access the currently active step via self.steps.current which you could use in your get_template_names view to return a path containing the step.
class AddWizard(SessionWizardView):
def get_template_names(self):
return ['step_{0}_template.html'.format(self.steps.current)]
I'm not sure if current is a string or integer or what - but one look at the view and you should find a useful "can't find template named X" error.
Related
The problem I've run into here is how to display some additional text in a form,
specifically the values of the previous step. The point is to show the user a message such as "you have chosen {{ value_from_step_0 }} as your starter, now choose a main course."
I can get that value from step 0 as follows:
class MenuWizard(SessionWizardView):
def get_form_kwargs(self, step=None):
kwargs = {}
if step == '1':
starter = self.get_cleaned_data_for_step('0')['starter']
kwargs.update({'starter': starter})
class MainCourseForm(forms.Form):
def __init__(self, *args, **kwargs):
starter = kwargs.pop('starter')
print "{0} chosen".format(starter) # This works
super(DomainForm, self).__init__(*args, **kwargs)
# What next?
If I don't pop the 'starter' value then the super call fails. But, how may I get that value into the relevant template? Presumably I need to do something further within init or override something else, but what?
First, note that 1.7 is unsupported, and the form wizards were removed from Django in 1.8 and are now in the third-party formtools library.
This isn't really a job for the form, but the view. SessionWizardView is a standard class-based view, and like most class-based views it allows you to override get_context_data to add elements to the template contents. See the documentation, which has an example of using self.steps.current to check the current step name.
My simple form looks like this:
class PropertyFilterForm(forms.Form):
property_type = forms.ModelChoiceField(queryset=Property.objects.values_list('type', flat=True).order_by().distinct())
property_type returns a flat list of String values to the drop-down list in my template.
Now, when i choose one of the values and hit "Submit" - i get the following error:
Select a valid choice. That choice is not one of the available
choices.
My view at the moment looks like this:
def index(request):
if request.method == 'POST':
form = PropertyFilterForm(request.POST)
if form.is_valid():
selected_type = form.cleaned_data['property_type']
properties = Property.objects.filter(type=selected_type)
else:
form = PropertyFilterForm()
properties = Property.objects.all()
return render(request, 'index.html', context=locals())
I read and re-read this question many times. It seems to be the same thing, but still I wasn't able to figure out the exact solution.
What I understood so far, is that I need to either explicitly specify a queryset for my form each time I call it in the view, or (better) specify the queryset in the init method of my form.
Could please someone elaborate on whether we need to specify a queryset the way i described above?
If yes, why? Haven't we already specified it in the form definition?
I would be really grateful for any code snippets
You want the user to select a string, not a property instance, so I think it would be a better fit to use a ChoiceField instead of a ModelChoiceField.
class PropertyFilterForm(forms.Form):
property_type = forms.ChoiceField(choices=[])
def __init__(self, *args, **kwargs):
super(PropertyFilterForm, self).__init__(*args, **kwargs)
self.fields['property_type'].choices = Property.objects.values_list('type', 'type').order_by('type').distinct()
The disadvantage of using a ChoiceField is we need to generate the choices in the __init__ method of the form. We have lost the nice functionality of the ModelChoiceField, where the queryset is evaluated every time the form is created.
It's not clear to me why Daniel recommended sticking with ModelChoiceField rather than ChoiceField. If you were to use ModelChoiceField, I think you'd have to subclass it and override label_from_instance. As far as I know, using values() is not going to work.
To specify the initial value, you can either hardcode it in the form definition,
class PropertyFilterForm(forms.Form):
property_type = forms.ChoiceField(choices=[], initial='initial_type')
or set it in the __init__ method,
self.fields['property_type'].initial = 'initial_type'
or provide it when instantiating the form:
form = PropertyFilterForm(request.POST, initial={'property_type': 'initial_type'})
I'm trying to implement some customize login through the custom methods in Django's models. I want to know if its possible to:
Get request.user in a custom method
Get the user that made the request in the method
Or pass an argument to the custom method
Thinking in doing something like this:
class OneModel(models.Model):
(...)
def viewed(self):
profile = request.user.profile
viewed = self.viewed_episodes.filter(user=profile).exists()
if viewed: return True
else: return None
Another possibility that came to my mind is this:
class OneModel(models.Model):
(...)
def viewed(self, user):
profile = user.profile
viewed = self.viewed_episodes.filter(user=profile).exists()
if viewed: return True
else: return None
But I think neither of this are possible. Maybe what I need is a template tag?
Second one is correct.
def viewed(self, user):
return self.viewed_episodes.filter(user=user.profile).exists() or None
With function based Django view it was simple to switch between several different views based on a condition, e.g. something like:
def base_view(request):
if some_condition():
return foo_view(request)
else:
return bar_view(request)
I can't find a simple way to do the same with the new class-based generic views. The only way I can think of is to redisrect, which I would like to avoid for various reasons:
def base_view(request):
if some_condition():
return redirect(reverse("name_of_url_to_class-based_view_foo"))
else:
return redirect("/url_to_class-based_view_bar/")
Any suggestions?
This is equivalent to your example with class based views.
class FooView(View):
pass
class BarView(View):
pass
class BaseView(View):
# staticmethod to avoid adding 'self' to the arguments
foo_view = staticmethod(FooView.as_view())
bar_view = staticmethod(BarView.as_view())
def dispatch(self, request, *args, **kwargs):
if some_condition():
return self.foo_view(request, *args, **kwargs)
else:
return self.bar_view(request, *args, **kwargs)
Even though the Django docs do say that the function based generic views are now deprecated I think the only reason to switch would be if you're writing less code.
If you're still set on switching, you'll want to first identify which class based views or mixins are most appropriate (single object, multiple objects, date based, forms, etc.). If the conditional was used to select a function that returns different context data / template to provide to the view, you can push the conditional down into an overridden get_queryset|get_context_data|get_object|get_template_names depending on your use case.
For example,
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(BaseView, self).get_context_data(**kwargs)
# Add in the publisher
if some_condition():
context['some_data'] = ...
else:
context['other_data'] = ...
return context
If all else fails and you're still determined to have class based views, you could probably also override get(self, request, *args, **kwargs) and do the switching there to the appropriate method. The detailed docs are getting better but I've still found myself poking through the source code to figure out how to accomplish what I want.
I've been pulling from various questions on StackOverflow to try to figure out how to work with ModelMultipleChoiceFields within a form. I almost have a working form that allows users to select languages to translate an article to. I created a form that takes a SourceArticle as the first constructor argument and uses it to specify the queryset for the languages field of my form.
class AddTargetLanguagesForm(forms.Form):
def __init__(self, article=None, *args, **kwargs):
super(AddTargetLanguagesForm, self).__init__(*args, **kwargs)
self.fields['languages'].queryset = Language.objects.exclude(
Q(id = article.language.id) |
Q(id__in=[o.id for o in article.get_target_languages()]) |
Q(code="templates"))
languages = forms.ModelMultipleChoiceField(_("Languages"))
Note that my AddTargetLanguagesForm is not based on a ModelForm, because it is not directly related to any of my model objects.
When I render the form for the first time, it correctly provides me with languages that (a) aren't the source language, (b) aren't already selected, and (c) aren't the special "templates" language. However, when I try to post my form, I get the following error:
AttributeError: 'QueryDict' object has
no attribute 'language'
I assume that this is related to how forms work in Django, but I'm pretty new. Rather than accepting a SourceArticle as the first parameter in my constructor, a QueryDict is placed instead. I assume that this contains the POST params from the request. How do I need to modify my code to allow it to capture the selected languages?
Here is a copy of my view, if it helps you see how I'm using the form.
#login_required
def add_target_languages(request, aid, template_name="wt_articles/add_target_languages.html"):
"""
Adds one or more target language translations to a source article.
"""
content_dict = {}
# Fetch the article
no_match = False
sa_set = SourceArticle.objects.filter(id=aid)
if len(sa_set) < 1:
no_match = True
content_dict['no_match'] = no_match
else:
article = sa_set[0]
content_dict['article'] = article
if request.method == "POST":
target_language_form = AddTargetLanguagesForm(request.POST)
if target_language_form.is_valid():
languages = target_language_form.cleaned_data['languages']
article.add_target_languages(languages)
return HttpResponseRedirect('/articles/list')
else:
target_language_form = AddTargetLanguagesForm(article)
content_dict['target_language_form'] = target_language_form
return render_to_response(template_name, content_dict,
context_instance=RequestContext(request))
This line is your problem:
target_language_form = AddTargetLanguagesForm(request.POST)
That's the standard way of instantiating a form from a POST, but the trouble is that you've rewritten the method signature of AddTargetLanguagesForm.__init__:
def __init__(self, article=None, *args, **kwargs):
so that the first positional argument (after the automatic self), is article. You could change the instantiation, but I prefer to do this:
def __init__(self, *args, **kwargs):
article = kwargs.pop('article', None)
super(AddTargetLanguagesForm, self).__init__(*args, **kwargs)
if article is not None:
...etc...