I am trying to edit existing objects using my Django FormWizard. I am following the technique described in this blog post, but it does not work. Here is my edit view:
#login_required
def edit_wizard(request, id):
thing = get_object_or_404(Thing, pk=id)
if thing.user != request.user:
raise HttpResponseForbidden()
else:
initial = {0: {'year': thing.year,
'make': thing.make,
'series': thing.series,
....etc.
},
1: {'condition': thing.condition,
....etc.
},
}
form = CreateWizard.as_view([StepOneForm, StepTwoForm, StepThreeForm], initial_dict=initial)
return form(context=RequestContext(request), request=request)
Can you help me figure out how to provide the initial data to the Wizard so that I can allow users to edit their objects? Thanks for your ideas!
EDIT: (2/18/13)
Was getting a:
TypeError at /edit/10/ __init__() takes exactly 1 argument (3 given)
This was solved by #sneawo's answer below, but still no initial data is passed, and the wizard instead creates new objects.
EDIT: (2/19/13)
class CreateWizard(SessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT))
def done(self, form_list, **kwargs):
instance = Thing()
for form in form_list:
for field, value in form.cleaned_data.iteritems():
setattr(instance, field, value)
instance.user = self.request.user
instance.save()
return render_to_response('wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
As per the documentation, for Django 1.4+ you pass the initial data in initial_dict keyword argument. For previous versions(1.3, it seems it wasn't there before 1.3) the keyword argument was initial. Also, the keys for steps in your initial data dict should be strings not integers.
initial = {'0': {'year': thing.year,
'make': thing.make,
'series': thing.series,
....etc.
},
'1': {'condition': thing.condition,
....etc.
},
}
UPDATE:
To update the same object you have to set the id also, otherwise there is no way for django to know which object to update. A simple way to do it is to pass the id in a hidden field, but you have to do the user permission check again in your (done) method.
initial = {0: {'id': thing.id,
class CreateWizard(SessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT))
def done(self, form_list, **kwargs):
id = form_list[0].cleaned_data['id']
thing = get_object_or_404(Thing, pk=id)
if thing.user != self.request.user:
raise HttpResponseForbidden()
else:
instance = Thing()
for form in form_list:
for field, value in form.cleaned_data.iteritems():
setattr(instance, field, value)
instance.user = self.request.user
instance.save()
return render_to_response('wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list],})
and of course StepOneForm should have id with hidden field:
class StepOneForm(forms.Form):
id = forms.IntegerField(widget=forms.HiddenInput)
Try to use form = CreateWizard.as_view([StepOneForm, StepTwoForm, StepThreeForm], initial=initial)
Related
I have the following form:
class SkuForm(forms.Form):
base_item = forms.ModelChoiceField(queryset=BaseItem.objects.none())
color_or_print = forms.ModelMultipleChoiceField(queryset=Color.objects.none())
material = forms.ModelMultipleChoiceField(queryset=Material.objects.none())
size_group = forms.ModelMultipleChoiceField(queryset=Size_Group.objects.none())
my view:
def sku_builder(request):
if request.method == "POST":
user = request.user
form = SkuForm(request.POST)
if form.is_valid():
base_item = form.cleaned_data['base_item']
colors = filter(lambda t: t[0] in form.cleaned_data['color_or_print'], form.fields['color_or_print'].choices)
materials = filter(lambda t: t[0] in form.cleaned_data['material'], form.fields['material'].choices)
size_groups = filter(lambda t: t[0] in form.cleaned_data['size_group'], form.fields['size_group'].choices)
return render(request, 'no_entiendo.html', {'colors': colors, })
else:
return HttpResponse("form is not valid")
user = request.user
form = SkuForm()
form.fields['base_item'].queryset = BaseItem.objects.filter(designer=user)
form.fields['color_or_print'].queryset = Color.objects.filter(designer=user)
form.fields['material'].queryset = Material.objects.filter(designer=user)
form.fields['size_group'].queryset = Size_Group.objects.filter(designer=user)
return render(request, 'Disenador/sku_builder.html', {'form': form,})
The problem is that Im only receiving the "form is not valid message" I have no idea why it is not valid as the Form is only made of choices, so no typo error. Also I have no feedback from the system to debug, or don't know where to search.
*what happens after form.is_valid is not the complete code
UPDATE:
I placed the {{ form.errors}} and got this:
color_or_print
Select a valid choice. 6 is not one of the available choices.
base_item
Select a valid choice. That choice is not one of the available choices.
size_group
Select a valid choice. 2 is not one of the available choices.
In size_group and color_or_print the number is the pk (but is only showing one item, 2 were selected), not sure what is happening in base_item. Should I extract the values through a:
get_object_or_404 ?
and what can I do with base_item? here is an image of the information
posted from the debug_toolbar
Instead of sending an HttpResponse, you need to render the html with the form if the form is invalid.
if form.is_valid():
# Do your operations on the data here
...
return render(request, 'no_entiendo.html', {'colors': colors, })
else:
return render(request, 'Disenador/sku_builder.html', {'form': form,})
Also if you're using model choice fields, the ideal place to define your queryset is in your form's __init__ method
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
self.fields['base_item'].queryset = BaseItem.objects.filter(designer=user)
# define more querysets here as you require
...
super(SkuForm, self).__init__(*args, **kwargs)
You can change the queryset in view. But that as far as I understand is a way to override whatever you have set in your forms. It should normally be set in __init__.
try to render {{form.errors}} in your template
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 trying to create a new table in my MySQL DB with a single field to record an incrementing value. However I keep getting an error:
Exception Value: global name 'total_max_counter' is not defined
Can someone tell me where I am going wrong?
views.py
from survey.models import TotalCounter
def done(self, form_list, **kwargs):
total_counter = TotalCounter.objects.get_or_create(total_max_counter)
total_counter.survey_wizard_total += 1
total_counter.save()
for form in form_list:
form.save()
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
models.py
class TotalCounter(models.Model):
total_max_counter = models.SmallIntegerField(default=0)
def __unicode__(self):
return self
Firstly, in get_or_create(), you need to specify the argument in the form of kwargs. Then only, it will look up an object with the given kwargs, creating one if necessary. kwargs may be empty if your model has defaults for all fields.
The function definition for get_or_create() is:
get_or_create(defaults=None, **kwargs)
Secondly, you are trying to update the survey_wizard_total field of TotalCounter object whereas your model has no such field defined in it. It has only the field total_max_counter in it. You will also need to correct that in your code.
This should work for you:
from django.db.models import F
from survey.models import TotalCounter
def done(self, form_list, **kwargs):
total_counter = TotalCounter.objects.get_or_create(total_max_counter__gte=0)[0]
total_counter.total_max_counter = F('total_max_counter') + 1 # increment value of `total_max_counter` by 1
total_counter.save() # save the object
for form in form_list:
form.save()
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
You are using get_or_create method in a wrong way:
total_counter = TotalCounter.objects.get_or_create(total_max_counter)
You are trying to get object from database but you are not making any comparison.
It should be something like:
total_counter = TotalCounter.objects.get_or_create(total_max_counter=<some value to match against>)
Even after this correction if your query runs, then an object for TotalCounter will be returned which have no attribute 'survey_wizard_total'
total_counter.survey_wizard_total += 1
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)
I am working with modelformset, and am a little stuck. I am passing, say 20 forms using modelformsetfactory. These forms are constructed and displayed in the page. When I return after the posting, I only want some of these forms to be validated and saved, not all of them, depending upon the value of a model field.
I figured I could use queryset in the request.POST to limit the forms that I want in my formset that are to be validated. But this is not working. Is there any way I can limit the number of forms?
For the queryset that limits model instances I tried
formset = PaymentOptionFormSet(request.POST, queryset=payment_option_posted_queryset)
I get the following error:
IndexError at /seller/seller_profile/
list index out of range
Traceback:
File "/home/shagun/work/tinla/django/core/handlers/base.py" in get_response
100. response = callback(request, *callback_args, **callback_kwargs)
File "/home/shagun/work/tinla/web/views/user_views.py" in seller_profile
164. formset = PaymentOptionFormSet(request.POST, queryset=payment_option_posted_queryset)
File "/home/shagun/work/tinla/orders/forms.py" in __init__
400. super(BasePaymentOptionFormSet, self).__init__(*args,**kwargs)
File "/home/shagun/work/tinla/django/forms/models.py" in __init__
423. super(BaseModelFormSet, self).__init__(**defaults)
File "/home/shagun/work/tinla/django/forms/formsets.py" in __init__
47. self._construct_forms()
File "/home/shagun/work/tinla/django/forms/formsets.py" in _construct_forms
97. self.forms.append(self._construct_form(i))
File "/home/shagun/work/tinla/django/forms/models.py" in _construct_form
447. kwargs['instance'] = self.get_queryset()[i]
File "/home/shagun/work/tinla/django/db/models/query.py" in __getitem__
172. return self._result_cache[k]
Exception Type: IndexError at /seller/seller_profile/
Exception Value: list index out of range
My code looks like this:
def seller_profile(request):
from accounts.models import PaymentOption, PaymentMode
payment_options = PaymentOption.objects.select_related('payment_mode').filter(payment_mode__client__id=1)
payment_option_queryset = PaymentOption.objects.filter(payment_mode__client__id='1')
payment_option_posted_queryset = PaymentOption.objects.filter(payment_mode__client__id='1', is_active='1')
if request.user.is_authenticated():
PaymentOptionFormSet = modelformset_factory(PaymentOption, formset = BasePaymentOptionFormSet, extra=0, fields = ("payment_delivery_address", "bank_branch", "bank_ac_name", "bank_ac_type", "bank_ac_no", "bank_address", "bank_ifsc", "is_active"))
user = request.user.get_profile()
if request.method == "POST":#If the form has been submitted
form1 = SellerProfileForm(request.POST, instance = user)
form2 = SellerNotificationForm(request.POST, instance = user)
formset = PaymentOptionFormSet(request.POST, queryset=PaymentOption.objects.all())
counting = 0
for form in formset.forms:
counting +=1
print "count = ",counting
print formset.is_valid()
if form1.is_valid() and form2.is_valid:
form1.save()
form2.save()
else:
my_acct_ctxt = getMyAccountContext(request)
return render_to_response('seller/seller_profile.html',
{
'form1': form1,
'form2': form2,
'formset': formset,
'error1': form1.errors,
'error2': form2.errors,
'errorformset': formset.errors,
'payment_options': payment_options,
'acc': my_acct_ctxt,
},
context_instance=RequestContext(request))
else: #If the form has not been submitted
form1 = SellerProfileForm(instance = user)
form2 = SellerNotificationForm(instance = user)
formset = PaymentOptionFormSet(queryset=payment_option_queryset)
counter = 0
my_acct_ctxt = getMyAccountContext(request)
return render_to_response('seller/seller_profile.html',
{
'form1': form1,
'form2': form2,
'formset': formset,
'payment_options': payment_options,
'acc':my_acct_ctxt,
},
context_instance=RequestContext(request))
If you're needing to conditionally validate a formset, you can override the clean method of the formset. Here's an example of how I've done this within admin on an inline formset, which you can probably change to suit your needs as the Formset classes are pretty homogenous.
class MyInlineFormset(forms.models.BaseInlineFormSet):
def clean(self):
for form in self.forms:
try:
if form.cleaned_data:
delete = form.cleaned_data.get('DELETE')
if not delete:
my_field = form.cleaned_data.get('my_field', None)
if my_field:
if my_field == 'some_value':
#only validate the other values
#if the field you're looking for
#has a particular value, etc
another_field = form.cleaned_data.get('another_field')
#more validation here where you can raise
#forms.ValidationError()
except AttributeError:
pass
I hope that helps you out!