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!
Related
THE CONTEXT
I am trying to implement a tagging system for my project. The various plug-in solutions (taggit, tagulous) are each unsuitable in some way.
I would like to allow users to select from existing tags or create new ones in a Select2 tagging field. Existing tags can be added or removed without problem. My difficulty is in the dynamic generation and assignment of new tags.
MY APPROACH
Select2 helpfully renders the manually-entered tags differently in the DOM from those picked from the database via autocomplete. So upon clicking submit, I have javascript collect the new tags and string them together in the value of a hidden input, then delete them from the Select2 field to avoid any validation errors (the form otherwise POSTs the tag names as the ids, which throws a db error).
In the view, I iterate over the desired new tags. For each entry I create the new tag, then add it to the parent object's related set.
THE PROBLEM
While this successfully creates each tag (verified via Admin) it doesn't add it to the related set.
No errors are generated on the (clearly not succeeding) related set add.
The newly-generated tags are correctly instantiated and can be Select2-chosen and sucessfully assigned on a subsequent UpdateView, so I'm certain the problem lies in the view-assignment of the tags to the parent.
The same code executed via the Django shell work flawlessly, so I don't believe its a simple syntax error.
Thus the locus of the problem seems to be in the POST view code adding newly-generated tags to the parent, but I cannot see where the code goes astray.
Thanks for any insights or advice!
models.py:
class Recipe_tag(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
tag = models.CharField('Tag name',max_length=32,null=False,unique=True)
def __str__(self):
return str(self.tag)
class Recipe_base(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
name = models.CharField('Recipe name',max_length=128,null=False)
tags = models.ManyToManyField(Recipe_tag,related_name='recipes',null=True,blank=True)
def __str__(self):
return str(self.name)
The post portion of the view:
def post(self, request, *args, **kwargs):
self.object = None
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
form = RecipeUpdateTagsForm(request.POST,instance=r)
form_valid = form.is_valid()
if form_valid:
if form.has_changed:
f = form.save(commit=False)
clean = form.cleaned_data
f.addedTags = clean['addedTags']
if f.addedTags == 'placeholder':
pass
else:
new_tags = f.addedTags.split(',')
for new_tag in new_tags:
a = Recipe_tag(tag=new_tag)
a.save()
r.tags.add(a)
f.save()
form.save_m2m()
else:
pass
return self.form_valid(form)
else:
return self.form_invalid(form)
Doing further digging, I found a post on another site in which the OP was experiencing the same issues. The trick is to remove the m2m assignment from the "create" or "update" process entirely, because the final save() that occurs in form_valid will discard any changes to the parent's related set.
In my case, the solution was to punt the iteration/assignment of the new tags to form_valid, directly after the final save() occurs there:
def form_valid(self, form, **kwargs):
self.object = form.save()
addedTags = kwargs['addedTags']
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
if addedTags == 'placeholder':
pass
else:
new_tags = addedTags.split(',')
for new_tag in new_tags:
a = Recipe_tag(tag=new_tag)
a.save()
# print("debug // new tag: %s, %s" % (a.tag, a.id))
r.tags.add(a)
# print("debug // added %s to %s" % (a.tag,r.id))
return HttpResponseRedirect(self.get_success_url())
def get(self, request, *args, **kwargs):
self.object = Recipe_base.objects.get(id=self.kwargs.get('pk'))
recipe_name = self.object.name
recipe_id = self.kwargs.get('pk')
form = RecipeUpdateForm(instance=Recipe_base.objects.get(id=self.kwargs.get('pk')))
return self.render_to_response(self.get_context_data(form=form,recipe_name=recipe_name,recipe_id=recipe_id))
def post(self, request, *args, **kwargs):
self.object = None
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
form = RecipeUpdateForm(request.POST,instance=r)
form_valid = form.is_valid()
#print("debug // form_valid PASSED")
if form_valid:
if form.has_changed:
#print("debug // Form changed...")
f = form.save(commit=False)
#print("debug // Passes save C=F")
clean = form.cleaned_data
#print('debug // pre-existing tags: ',clean['tags'])
f.addedTags = clean['addedTags']
addedTags = clean['addedTags']
#print('debug // manual tags: ',clean['addedTags'])
f.save()
form.save_m2m()
#print("debug // Form saved")
else:
#print("debug // Form unchanged, skipping...")
pass
#print("debug // Reached successful return")
return self.form_valid(form, addedTags=addedTags,pk=r.id)
else:
#print("debug // Form fails validation")
#print("debug // Reached unsuccessful return")
return self.form_invalid(form)
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 make an AbstractEditForm (Inherited from ModelForm) form, from which there are multiple forms that would be inheriting from it. But I am getting this error :
Here is my forms.py
# This is the abstract form that I want to inherit other forms from
class AbstractEditForms(forms.ModelForm):
def __init__(self, id_fields=None, ref_field=None,
model=None, *args, **kwargs):
self.id_fields = id_fields
self.changed_fields = {}
self.ref_field = ref_field
self.model_ = model
self.ref_id_changed = False
self.check_ref_id()
try:
if 'id_fields' in kwargs.keys():
del kwargs['id_fields']
if 'ref_fields' in kwargs.keys():
del kwargs['ref_fields']
except KeyError as e:
print('Error in AbstractionEditForms :', str(e))
super(AbstractEditForms, self).__init__(*args, **kwargs)
# This is the form that I want to use
class SchemeEditForm(AbstractEditForms):
class Meta:
model = SchemeModel
exclude = ['created_on', 'financial_year']
widgets = {
'as_ref_id': forms.TextInput(attrs={'readonly': 'readonly',
'placeholder': 'Auto Generated '
}),
'admin_sanction_amount': forms.HiddenInput(),
'updated_on': forms.HiddenInput(),
}
views.py :
def edit_scheme_form_view(request, pk=None):
assert pk is not None, 'pk cannot be None, scheme edit form'
instance = get_object_or_404(SchemeModel, pk=pk)
id_fields = ['technical_authority', 'dept_name', ]
model = SchemeModel
ref_field = "as_ref_id"
if request.method == 'GET':
scheme_form = SchemeEditForm(None, id_fields=id_fields, ref_field="as_ref_id",
model=model, instance=instance)
context = {
'form': scheme_form
}
return render(request, 'Forms/forms/SchemeForm.html', context=context)
if request.method == 'POST':
scheme_form = SchemeEditForm(request.POST, id_fields=id_fields, ref_field="as_ref_id",
model=SchemeModel, instance=instance)
if scheme_form.is_valid():
instance = scheme_form.save()
return generate_success_page(request, instance,"Scheme Edit Success",
"Scheme Details - Edited", nav_dict=None,
util_dict=None)
else:
return render(request, 'Forms/forms/SchemeForm.html', {'form': scheme_form})
Error traceback:
Internal Server Error: /edit/admin-sanction-form/1/
Traceback (most recent call last):
File "C:\Python35\lib\site-packages\django\core\handlers\base.py", line 149, in get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Python35\lib\site-packages\django\core\handlers\base.py", line 147, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\karth\PycharmProjects\phc\edit_forms\views.py", line 17, in edit_scheme_form_view
model=model, instance=instance
TypeError: __init__() got multiple values for argument 'id_fields'
[04/Sep/2016 20:41:08] "GET /edit/admin-sanction-form/1/ HTTP/1.1" 500 63681
I understand that this is problem with initialization form either AbstractEditForm or the SchemeEditForm.
Any help would be much appreciated.
SchemeEditForm is interpreting the first positional argument you are passing it as being the id_fields argument. When you later try to pass id_fields by name, it thinks it is getting a duplicate of that argument and you are getting an error.
Try changing your __init__() method to accept arbitrary positional arguments before your keyword arguments like this:
def __init__(self, *args, id_fields=None, ref_field=None, model=None, **kwargs):
I am not sure if this will get you the results you want in terms of how your forms function, but it will get rid of the error you are seeing. Note that this method will not work in Python 2.X, only Python 3.
Using django-clever-selects to chain selects but the is_valid() method in the view is causing this error:
Traceback:
File "/Applications/djangostack-1.7.10-0/apps/django/django_projects/freshTest/myenv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
132. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Applications/djangostack-1.7.10-0/apps/django/django_projects/freshTest/megaForm/views.py" in newForm
29. if form.is_valid():
File "/Applications/djangostack-1.7.10-0/apps/django/django_projects/freshTest/clever_selects/forms.py" in is_valid
191. activate(self.language_code)
File "/Applications/djangostack-1.7.10-0/apps/django/django_projects/freshTest/myenv/lib/python3.4/site-packages/django/utils/translation/__init__.py" in activate
146. return _trans.activate(language)
File "/Applications/djangostack-1.7.10-0/apps/django/django_projects/freshTest/myenv/lib/python3.4/site-packages/django/utils/translation/trans_real.py" in activate
217. if language in _DJANGO_DEPRECATED_LOCALES:
Exception Type: TypeError at /newForm/
Exception Value: unhashable type: 'QueryDict'
views.py
def newForm(request):
if request.method == 'POST':
form = SimpleChainForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
return render(request, 'form1/current_form.html', {
'form': form,
'message': (request.POST['cat'],request.POST['columns']),
# 'error_message': "You didn't select a choice.",
})
#if GET or other method create blank form
else:
form=SimpleChainForm()
return render(request, 'form1/current_form.html', {'form': form})
The documentation says that django-clever-selects has been 'Tested on Django 1.4.5.', but I need to use 1.8. Is there a work-round/fix for this error? New to django so any help would be appreciated!
For anyone else struggling with this issue, the following worked for me:
https://github.com/filipefigcorreia/django-clever-selects/commit/4f6da07bb9e880aaaa188297f5866bcbf9c6cab6
https://github.com/PragmaticMates/django-clever-selects/issues/6
Firstly in clever_selects/forms.py remove these lines
def __init__(self, language_code=None, *args, **kwargs):
self.language_code = language_code
add in
def __init__(self, *args, **kwargs):
self.language_code = kwargs.get('language_code', None)
in my app, forms.py, remove reverse_lazy
ajax_url=reverse_lazy('ajax_chained_subtypes') ==> ajax_url='/ajax/chained-subtypes/',
generates a bytes string problem, solved with:
clever_selects/forms.py line 97
field.choices = field.choices + json.loads(data.content) ==>
field.choices = field.choices + json.loads(data.content.decode("utf-8"))
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)