I am trying to build a django form wizard to allow people to register for an
event. I can get through the form wizard and see data in the done method.
The problem is that I also need event_id passed into done also. How do I get
event_id from the url through the form wizard and into done? Simple example?
------- urls.py ---------
named_register_forms2 = (
('basicdata', SeatsForm),
('form2', AnotherForm),
)
urlpatterns = patterns('',
url(r'^register/(?P<event_id>\d+)/$', register_wizard, name='register_step'),
)
------ forms.py -----------
class SeatsForm(forms.ModelForm):
class Meta:
model = MyModel
fields = [ 'last_name', 'first_name', 'address1', 'address2',
'city', 'state', 'zipcode', 'phone_number', 'email']
def __init__(self, *args, **kwargs):
super(SeatsForm, self).__init__(*args, **kwargs)
class RegisterWizard(SessionWizardView):
#storage_name = 'formtools.wizard.storage.session.SessionStorage'
template_name = 'wizard_form.html'
def done(self, form_list, **kwargs):
data = {}
for form in form_list:
data.update(form.cleaned_data)
print data
# I need event_id right here. How to get it?
return render_to_response('done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
I think you will have to put that in the form and get it from there.
If its model form you can pass instance_dict param to the wizard view. instance_dict param. However in that case you will have to implement a wrapper view that will prepare the wizard view with these params. Something like this:
def wrapper_view(request, id):
#somecode
seats_instance = SeatsModel.objects.get(id=id)
another_instance = AnotherModel.objects.get(id=id)
inst_dict = { '0': seats_instance,
'1': another_instance
}
return RegisterWizard.as_view(named_register_forms2, instance_dict=inst_dict)(request)
class RegisterWizard(SessionWizardView):
#storage_name = 'formtools.wizard.storage.session.SessionStorage'
template_name = 'wizard_form.html'
def done(self, form_list, **kwargs):
data = {}
seatform= form_list[0]
seatinst = form.save()
#save other forms
...
#using seatinst get event id
return render_to_response('done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
The question is two years old, but for completeness, I've been solving this problem by overriding the dispatch method of the wizard class. This method is called by the wizard with the raw arguments from the URL dispatcher. You can modify the wizard's instance_dict (and presumably any other wizard member) before anything else happens.
class RegisterWizard(SessionWizardView):
#storage_name = 'formtools.wizard.storage.session.SessionStorage'
template_name = 'wizard_form.html'
def dispatch(self, request, id, *args, **kwargs):
self.instance_dict = {
'0': SeatsModel.objects.get(id=id),
'1': AnotherModel.objects.get(id=id),
}
return super(RegisterWizard, self).dispatch(request, *args, **kwargs)
def done(self, form_list, **kwargs):
data = {}
seatform= form_list[0]
seatinst = form.save()
#save other forms
...
#using seatinst get event id
return render_to_response('done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
I'm not sure there is any major functional advantage, but it feels like the wizard is a little more encapsulated doing it this way. In addition I've no idea if this was the intended use of the dispatch method.
I suppose if one were to inherit RegisterWizard the behaviour of setting instance_dict with objects from SeatsModel and AnotherModel would be available without the need to make user of a wrapper function; that might be the only actual advantage of doing it this way.
Related
i have a view method such as
#login_required(login_url='/users/login')
def my_submission(request,submission_id):
submission = Submission.objects.get(pk=submission_id)
return render(request, "assignments/mysubmission.html", {
"submission": submission
})
I was wondering if there is a way to pass submission_id which is the second param to user passes test decorator so that i can do something like
#user_passes_test(lambda u: u.id == Submission.objects.get(pk=submission_id).user.id, login_url='/')
thanks for the guide in advance.
You should just write it as a check in the view:
#login_required(login_url='/users/login')
def my_submission(request,submission_id):
submission = Submission.objects.get(pk=submission_id)
if submission.user_id != request.user.pk:
return redirect('/')
return render(request, "assignments/mysubmission.html", {
"submission": submission
})
Update
If you use this many times, consider implementing it is a class-based view, so you could inherit and utilize some extra features:
class UserIsOwnerMixin(AccessMixin):
"""Verify that the user the owner of related object."""
owner_id_field = 'user_id'
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or getattr(self.get_object(), self.owner_field) != request.user.pk:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class SubmissionView(UserIsOwnerMixin, DetailView):
template = "assignments/mysubmission.html"
model = Submission
context_object_name = "submission"
login_url = '/users/login'
The following code is working but I wonder if there is a more elegant way of doing. I have to pass the specie_id so I can filter the breeds to the corresponding specie. I can pass the specie_id to the view but I also have the information in the Resident model ("specie").
both get() and post() have nearly the same code, passing the specie_id.
The View
class ResidentUpdate(UpdateView):
model = Resident
template_name = "administration/resident_form.html"
form_class = ResidentCreateForm
def get(self, request, pk):
initial = self.model.objects.get(id=pk)
form = self.form_class(instance=initial, specie_id=initial.specie.id)
return render(request, self.template_name, {"form": form})
def post(self, request, pk):
initial = self.model.objects.get(id=pk)
form = self.form_class(request.POST, specie_id=initial.specie.id, instance=initial)
if form.is_valid():
form.save()
return redirect("resident_detail", pk)
return render(request, self.template_name, {"form", form})
The Form
class ResidentCreateForm(ModelForm):
class Meta:
model = Resident
fields = [
"name",
"specie",
"breed",
"gender",
"gender_status",
"birth_date",
"organization",
"social_behaviors",
"notes",
]
widgets = {
"birth_date": DateInput(attrs={"class": "flatpickr"}),
"specie": HiddenInput(),
}
def __init__(self, *args, **kwargs):
self.specie_id = kwargs.pop("specie_id", None)
super(ResidentCreateForm, self).__init__(*args, **kwargs)
self.fields["specie"].initial = self.specie_id
self.fields["breed"].queryset = Breed.objects.for_specie(self.specie_id)
EDIT :
#Alasdair's answer is good and I think I perfected it a little more. My form is used for the create view too. So I added a check to see if I have the specie_id in kwargs (create) or if I have to use the specie_id from the instance (update)
def __init__(self, *args, **kwargs):
self.specie_id = kwargs.pop("specie_id", None)
super(ResidentForm, self).__init__(*args, **kwargs)
if not self.specie_id:
self.specie_id = self.instance.specie.id
self.fields["specie"].initial = self.specie_id
self.fields["breed"].queryset = Breed.objects.for_specie(self.specie_id)
It looks like you can do self.fields["breed"].queryset = Breed.objects.for_specie(self.initial.specie_id), then you don't need to pass in specie_id to the form.
class ResidentCreateForm(ModelForm):
class Meta:
model = Resident
fields = [
"name",
"specie",
"breed",
"gender",
"gender_status",
"birth_date",
"organization",
"social_behaviors",
"notes",
]
widgets = {
"birth_date": DateInput(attrs={"class": "flatpickr"}),
}
def __init__(self, *args, **kwargs):
super(ResidentCreateForm, self).__init__(*args, **kwargs)
self.fields["breed"].queryset = Breed.objects.for_specie(self.instance.specie_id)
Note I've removed the specie hidden input above, I don't think it's necessary.
The UpdateView takes care of passing instance to the form, so you can simplify the view.
from django.urls import reverse
class ResidentUpdate(UpdateView):
model = Resident
template_name = "administration/resident_form.html"
form_class = ResidentCreateForm
def get_success_url(self):
"""Redirect to resident_detail view after a successful update"""
return reverse('resident_detail', args=[self.kwargs['pk']]
I think a better approach would be overriding the get_form_kwargs method in your views.
def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
form_kwargs.update({'specie_id': self.kwargs.get('specie_id')})
return form_kwargs
Currently Happening:
Dynamically generated form and form fields are being displayed.
Enter some data into the said fields, but self.get_all_cleaned_data() returns nothing.
Form returns to page 0 instead of submitting the form and using done()
What I want to happen:
- Data in fields to be retained and displayed when going back, or to the confirmation page
- Form to actually submit and use done() to process and save
The following the my forms.py
class OrderForm(forms.Form):
class Meta:
localized_fields = ('__all__',)
def __init__(self, *args, **kwargs):
self.fields = kwargs.pop('fields')
fields = self.fields
super(OrderForm, self).__init__(*args, **kwargs)
if not isinstance(fields, str):
for i in fields.fields.all():
widget = forms.TextInput()
_type = forms.CharField
if i.field_type == Field.TEXTAREA_FIELD:
widget = forms.Textarea
...
self.fields[i.name] = _type(**fields)
This is supposed to get Database created forms and field data and generate fields accordingly. For example:
Form A has fields:
Name (Regular Text Field)
Address (Textarea)
The above code will then generate fields for those.
The next block of code is from my views.py file
FORM_TEMPLATES = {
"0": 'order/details.html',
"1": 'order/details.html',
"2": 'order/details.html',
"3": 'order/details.html',
"4": 'order/details.html',
"confirm": 'order/confirm.html',
}
class Order(SessionWizardView):
form_list = [OrderForm]
def get_current_step_form(self, company, *args, **kwargs):
step_form = [Form.objects.all()]
step_form.append('Confirm')
return step_form
def get_context_data(self, form, **kwargs):
context = super(Order, self).get_context_data(form=form, **kwargs)
# Returns {}, but I want this to return all previous field values
context.update({
'all_data': self.get_all_cleaned_data(),
})
return context
def post(self, *args, **kwargs):
go_to_step = self.request.POST.get('wizard_goto_step', None)
form = self.get_form(data=self.request.POST)
current_index = self.get_step_index(self.steps.current)
goto_index = self.get_step_index(go_to_step)
if current_index > goto_index:
self.storage.set_step_data(self.steps.current,
self.process_step(form))
self.storage.set_step_files(self.steps.current,
self.process_step_files(form))
return super(Order, self).post(*args, **kwargs)
def get_form(self, step=None, data=None, files=None):
"""
Get the form and add to form_list
"""
form = super(Order, self).get_form(step, data, files)
company = ...
get_forms = self.get_current_step_form(company=company)
form_list_value = dict(self.form_list)['0']
while len(self.form_list.items()) < len(get_forms):
self.form_list.update({str(len(self.form_list.items())): form_list_value})
return form
def done(self, form_list, **kwargs):
return HttpResponse("View")
done() is a work in progress, but it doesn't even seem to reach that point, as it keeps going from (for example) Form 0-1-2-3-0-...
The confirm form will not have any field values form the previous pages and will only return {}
Any help would be appreciated,
Thanks
I am trying to create a Django page where something can be updated and something can be viewed in a paginated table. The model looks like this:
class CostGroup(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse(
'costgroup_detail',
kwargs={
'costgroup_pk': self.pk,
}
)
class Cost(models.Model):
cost_group = models.ForeignKey(CostGroup)
amount = models.DecimalField(max_digits=50, decimal_places=2)
def get_absolute_url(self):
return reverse(
'cost_detail',
kwargs={
'cost_pk': self.pk,
}
)
So the edit form is for the name and description fields of the CostGroup model and the table should show a list of the 'amounts`
I previously had it working by just having an UpdateView for the form and the table included in the form template. Now though, as I want to include pagination on the table, I need to use two views on the same page. The page I have designed should look something like this in the end:
I am not worried about the styling at the moment my main focus at the moment is getting the form and the table on the same page. In its current state the only thing that I don't have is the pagination for the table:
The view currently looks like this:
class CostDetail(UpdateView):
model = models.Cost
pk_url_kwarg = 'cost_pk'
template_name = 'main/cost_detail.html'
form_class = forms.CostDetailEditForm
success_url = reverse_lazy('cost_list')
I have a feeling that leveraging the underlying mixins that the Django CBVs use is probably the way to go but I am not sure how to begin with this.
Any help would be much appreciated
Thanks for your time
(This clarification seemed to work better as a new answer)
It looks like you're dealing with both of the tables. The object level is using CostGroup, while the List view is showing the child records from Cost linked to a CostGroup. Assuming that is true, here's how I would proceed:
class CostDetail(ModelFormMixin, ListView):
model = CostGroup # Using the model of the record to be updated
form_class = YourFormName # If this isn't declared, get_form_class() will
# generate a model form
ordering = ['id']
paginate_by = 10
template_name = 'main/cost_detail.html' # Must be declared
def get_queryset(self):
# Set the queryset to use the Cost objects that match the selected CostGroup
self.queryset = Cost.objects.filter(cost_group = get_object())
# Use super to add the ordering needed for pagination
return super(CostDetail,self).get_queryset()
# We want to override get_object to avoid using the redefined get_queryset above
def get_object(self,queryset=None):
queryset = CostGroup.objects.all()
return super(CostDetail,self).get_object(queryset))
# Include the setting of self.object in get()
def get(self, request, *args, **kwargs):
# from BaseUpdateView
self.object = self.get_object()
return super(CostDetail,self).get(request, *args, **kwargs)
# Include the contexts from both
def get_context_data(self, **kwargs):
context = ModelFormMixin.get_context_data(**kwargs)
context = ListView.get_context_data(**context)
return context
# This is the post method found in the Update View
def post(self, request, *args, **kwargs):
# From BaseUpdateView
self.object = self.get_object()
# From ProcessFormView
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
I haven't tried to run this, so there may be errors. Good luck!
(Remember ccbv.co.uk is your friend when digging into Class-based Views)
An app I'm working on now uses a similar approach. I start with the ListView, bring in the FormMixin, and then bring in post() from the FormView.
class LinkListView(FormMixin, ListView):
model = Link
ordering = ['-created_on']
paginate_by = 10
template_name = 'links/link_list.html'
form_class = OtherUserInputForm
#=============================================================================#
#
# Handle form input
#
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
def get_success_url(self):
return reverse('links')
You may also wish to override get_object(), get_queryset(), and get_context().
I have a view that inherits from the generic CreateView and overrides the get_initial method like so:
class PosterVisualCreateView (ModelFormMixin, generic.edit.CreateView, ObjectClassContextMixin):
model = Poster
valid_message = "Successfully created object."
template_name = "poser/create_poster_visual.html"
def get_form_class (self):
return super(PosterVisualCreateView, self).get_form_class(extra="CreateVisual")
def get_form_kwargs (self):
kwargs = super(PosterVisualCreateView, self).get_form_kwargs()
kwargs.update({
"company": self.request.company
})
return kwargs
def get_context_data (self, **kwargs):
context = super(PosterVisualCreateView, self).get_context_data(**kwargs)
context.update({
"company": self.request.company,
})
return context
def get_initial (self):
initial = super(PosterVisualCreateView, self).get_initial()
initial.update({
"company": self.request.company,
"template": self.request.company.template_set.all().first()
})
return initial
def form_valid(self, form):
success_url = super(PosterVisualCreateView, self).form_valid(form)
attributes = form.instance.create_attributes()
for attribute in attributes:
attribute.poster = form.instance
attribute.save()
form.instance.save()
form.instance.render_html(commit=True)
form.instance.save()
return success_url
#method_decorator(login_required)
def dispatch (self, *args, **kwargs):
return super(PosterVisualCreateView, self).dispatch(*args, **kwargs)
The page renders this form:
class PosterFormCreateVisual (CompanyHiddenForm):
"""Create form for Posters."""
template = fields.ModelChoiceField(widget=forms.RadioSelect, queryset=Template.objects.all())
product = fields.ModelChoiceField(widget=forms.Select, queryset=Product.objects.all(),
required=False)
class Meta:
model = Poster
fields = ("template", "product", "company")
def __init__ (self, *args, **kwargs):
company = kwargs.pop("company", None)
assert company is not None, "Company is required to create attribute form."
super(PosterFormCreateVisual, self).__init__(*args, **kwargs)
self.fields["template"].queryset = company.template_set.all()
self.fields["product"].queryset = company.product_set.all()
The initial value should be the first item in the radio selection for template but this isn't the case, can anyone help me out here?
Try this:
"template": self.request.company.template_set.all().first()
But as far as I understand you original code should work too.
BTW, how you tested the form? By hitting the "Refresh" button or Ctrl-R/F5 key? Some browsers reload page but save the previously selected/entered form values. To check initial values you should reload the form page by clicking on the address bar (or pressing Ctrl-L) and then pressing the Enter key.