Why the form is invalid at FormView? - django

All fields are filled. But for some reason does not go into the form_valid method, but it goes into form_invalid. Why form is invalid?
forms.py
class CreditFilterForm(forms.Form):
CURRENCY_CHOICES = (
('KZT', _('KZT')),
('USD', _('USD')),
)
PERIOD_CHOICES = (
('1', _('One year')),
('2', _('Two')),
('3', _('Three'))
)
sum = forms.CharField(widget=forms.NumberInput(attrs={'id': "sum", 'class':"forminput-text"}))
currency = forms.ChoiceField(choices = CURRENCY_CHOICES, widget=forms.Select(attrs={'name': "minbeds", 'id':"currency"}))
term = forms.ChoiceField(choices = PERIOD_CHOICES, widget=forms.Select(attrs={'id':"term", 'name': "minbeds"}))
views.py
class CreditsList(ListView):
model = Credit
template_name = 'credits/credit_listing.html'
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
little_form = CreditFilterForm(self.request.GET or None, prefix="little")
...
class LittleForm(FormView):
form_class = CreditFilterForm
template
<form action="{% url 'little_form' %}" method="post">
{% csrf_token %}
{{ little_form.as_p }}
<input type="submit" name="{{ little_form.prefix }}" value="Submit">
</form>

The prefix is applied to all form input widgets. But in your LittleForm view, you did not use that prefix.
You can add it by overriding the prefix attribute [Django-doc]:
class LittleForm(FormView):
form_class = CreditFilterForm
prefix = 'little'
Note: I advice to rename LittleForm to LittleFormView. It makes it more clear what that class is, and it will avoid a "collision" if you later construct a form with the same name.

Related

Django Dynamically Add Forms Based On Key Value Pairs List

Is there a way to dynamically create formsets based on an API response. This would easily be done in JS, however, the app is running Django's MVT and I would like to create the form dynamically within python. Also, the API response won't come in until after the Item model is created. This form will be the second step in a flow.
models.py
class Item(models.Model):
name = models.CharField(max_length=50)
class ItemAspect(models.Model):
name = models.CharField(max_length=50)
value = models.CharField(max_length=50)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
API resposne:
{
[
{
"key": "Name",
"value_list": [
"value0",
...
]
},
...
]
}
Desired form:
<form>
...
<label for="value-0"> {{ key }}</label>
<select name="value-0" id="value-0">
<option value="{{ value_list[0] }}"> {{ value_list[0] }} </option>
...
</select>
<---! The below would be handled in the view, just adding for context -->
<input style="display: hidden;" name="name-0" value="{{ key }}">
...
</form>
My best guess is that it could be done with crispy's form_helper in a view after the API response is received.
For those that might come across this, here is my current solution (also open to improvements):
forms.py
class ItemAspectForm(forms.ModelForm):
class Meta:
model = ItemAspect
exclude = ("item",)
views.py
class ItemAspectCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = ItemAspectForm
form_class = ItemAspectForm
template_name = "inventory/item_aspect_form.html"
success_message = _("Item aspect successfully created")
def get_context_data(self, **kwargs):
item = Item.objects.get(id=self.kwargs["id"])
aspects = [Aspect(**aspect) for aspect in get_item_aspects_for_category(item)["aspects"]]
context = super().get_context_data()
ItemAspectFormSet = modelformset_factory(
model=ItemAspect,
form=ItemAspectForm,
can_delete=True,
extra=len(aspects)
)
if self.request.POST:
context["formset"] = ItemAspectFormSet(self.request.POST)
else:
context["formset"] = ItemAspectFormSet()
for aspect, form in zip(aspects, context["formset"]):
if aspect.has_values:
form.fields["value"] = forms.ChoiceField(
choices=((value, value) for value in aspect.values)
)
form.fields["value"].required = aspect.is_required
form.fields["value"].label = aspect.name
form.fields["name"].initial = aspect.name
form.fields["name"].widget = forms.HiddenInput()
return context
def form_valid(self, form):
assert (
self.request.user.is_authenticated
) # for mypy to know that the user is authenticated
context = self.get_context_data()
formset = context["formset"]
item = Item.objects.get(id=self.kwargs["id"])
if formset.is_valid():
aspect_forms = formset.save(commit=False)
for aspect_form in aspect_forms:
aspect_form.item = item
aspect_form.save()
return super().form_valid(form)
item_aspects_create = ItemAspectCreateView.as_view()

Form not valid. ID: Select a valid choice. That choice is not one of the available choices

When I run this code, even the whole data is correct.
demobilized_formset.is_valid() gives me False and demobilized_formset.errors gives {u'id': [u'Select a valid choice. That choice is not one of the available choices.']}
I don't know why an error comes to id as {u'id': [u'Select a valid choice. That choice is not one of the available choices.']}
managers.py
class DemobilizedEmployee(models.Manager):
use_for_related_fields = True
def get_queryset(self):
# When I use filter instead of exclude it works fine.
return super(DemobilizedEmployee, self).get_queryset().exclude(
demobilized_date=None)
models.py
class PurchasedEmployees(AuditDeleteSafeModel):
purchase = models.ForeignKey(Purchase)
employee = models.ForeignKey(Employee)
category = models.CharField(max_length=32)
mobilized_date = models.DateField()
demobilized_date = models.DateField(null=True, blank=True)
period_worked = models.PositiveSmallIntegerField(null=True, blank=True)
mobilized_emp = MobilizedEmployee()
demobilized_emp= DemobilizedEmployee()
objects = # Is also an custom Manager which changes deleted=True in Abstract Model
class Meta:
db_table = 'purchased_employees'
forms.py
class DetailMobilizeForm(forms.ModelForm):
class Meta:
model = PurchasedEmployees
fields = ('employee', 'category', 'mobilized_date',
'demobilized_date', 'period_worked')
DetailMobilizeInlineFormSet = modelformset_factory(
PurchasedEmployees, can_delete=False, extra=0, form=DetailMobilizeForm)
views.py
def update_demobilize_emp(request, id=None):
purchase = get_object_or_404(Purchase, id=id) if id else Purchase()
demobilized_queryset = PurchasedEmployees.demobilized_emp.filter(
purchase=purchase)
if request.POST:
demobilized_formset = DetailMobilizeInlineFormSet(
data=request.POST,
prefix='demobilized_form')
if demobilized_formset.is_valid():
demobilized_formset.save()
status_code, res = 200, {'status': 'true'}
else:
for form in demobilized_formset:
import ipdb; ipdb.set_trace()
errors = {}
errors[demobilized_formset.prefix + '_errors'] =\
demobilized_formset.errors
status_code, res = 202, {'error': errors , 'status': 'false'}
return HttpResponse(
json.dumps(res), content_type='application/json',
status=status_code)
else:
demobilized_formset = DetailMobilizeInlineFormSet(
queryset=demobilized_queryset,
prefix='demobilized_form')
return render_to_response(
'purchase/update_demobilized_employees.html', {
'demobilized_formset': demobilized_formset, 'purchase': purchase},
context_instance=RequestContext(request))
template
<div class="field">
{{ demobilized_formset.management_form }}
<div class="box">
{% for forms in demobilized_formset.forms %}
<div class="box_item">
{{ forms }}
<br class="clear">
</div>
{% endfor %}
</div>
And
...
<label for="id_demobilized_form-0-period_worked">Period worked:</label>
<input id="id_demobilized_form-0-period_worked" type="number" value="100" name="demobilized_form-0-period_worked" min="0">
<input id="id_demobilized_form-0-id" type="hidden" value="8" name="demobilized_form-0-id">
<ul class="errorlist">
<li>Select a valid choice. That choice is not one of the available choices.</li>
</ul>

Django passing object ID in hiddeninput by populating

I have an form which allows a user to edit an object description.
How can I populate an object ID in a form's hidden input value.
What I done so far is I added an field called hidden_field in forms.py but it only show the hidden_field . How can I link the hidden_field with the object ID
models.py
class School(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=55)
description = models.CharField(max_length=300,blank=True)
forms.py
class SchoolDescriptionForm(forms.ModelForm):
description = forms.CharField(widget=forms.Textarea,max_length=300)
hidden_field = forms.CharField(widget=forms.HiddenInput())
class Meta:
model = School
fields = ()
views.py
def SchoolEditor(request,school_id):
school = School.objects.get(pk=school_id,user=request.user)
form = SchoolDescriptionForm(instance=school) # I want to populate the object ID
return render(request,'schooleditor.html',{'school':school,'form':form})
template
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type = "submit" value= "save" />
{{ form.field.as_hidden }}
</form>
Change hidden_field to id and tell Django to include the School's id.
class SchoolDescriptionForm(forms.ModelForm):
description = forms.CharField(widget=forms.Textarea,max_length=300)
id = forms.CharField(widget=forms.HiddenInput())
class Meta:
model = School
fields = ('id', 'name', 'description')
EDIT:
If you want to conserve hidden_field as name you should then add a custom init method:
def __init__(self, *args, **kwargs):
super(SchoolDescriptionForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['hidden_field'].initial = instance.id
Just pass the object id in the form initial:
def SchoolEditor(request,school_id):
initial = {}
school = School.objects.get(pk=school_id,user=request.user)
if school:
initial.update({'hidden_field': school.id})
form = SchoolDescriptionForm(instance=school, initial=initial) # I want to populate the object ID
return render(request,'schooleditor.html',{'school':school,'form':form})

Django Forms: pass parameter to form

How do I pass a parameter to my form?
someView()..
form = StylesForm(data_dict) # I also want to pass in site_id here.
class StylesForm(forms.Form):
# I want access to site_id here
You should define the __init__ method of your form, like that:
class StylesForm(forms.Form):
def __init__(self,*args,**kwargs):
self.site_id = kwargs.pop('site_id')
super(StylesForm,self).__init__(*args,**kwargs)
of course you cannot access self.site_id until the object has been created, so the line:
height = forms.CharField(widget=forms.TextInput(attrs={'size':site_id}))
makes no sense. You have to add the attribute to the widget after the form has been created. Try something like this:
class StylesForm(forms.Form):
def __init__(self,*args,**kwargs):
self.site_id = kwargs.pop('site_id')
super(StylesForm,self).__init__(*args,**kwargs)
self.fields['height'].widget = forms.TextInput(attrs={'size':site_id})
height = forms.CharField()
(not tested)
This is what worked for me. I was trying to make a custom form . This field in the model is a charfield but I wanted a choice field generated dynamically .
The Form:
class AddRatingForRound(forms.ModelForm):
def __init__(self, round_list, *args, **kwargs):
super(AddRatingForRound, self).__init__(*args, **kwargs)
self.fields['name'] = forms.ChoiceField(choices=tuple([(name, name) for name in round_list]))
class Meta:
model = models.RatingSheet
fields = ('name', )
The Views:
interview = Interview.objects.get(pk=interview_pk)
all_rounds = interview.round_set.order_by('created_at')
all_round_names = [rnd.name for rnd in all_rounds]
form = forms.AddRatingForRound(all_round_names)
return render(request, 'add_rating.html', {'form': form, 'interview': interview, 'rounds': all_rounds})
The Template:
<form method="post">
{% csrf_token %}
{% if interview %}
{{ interview }}
{% if rounds %}
{{ form.as_p }}
<input type="submit" value="Submit" />
{% else %}
<h3>No rounds found</h3>
{% endif %}
</form>
someView()..
form = StylesForm( 1, request.POST)
in forms.py
class StylesForm(forms.Form):
#overwrite __init__
def __init__(self,site_id,*args,**kwargs):
# call standard __init__
super().__init__(*args,**kwargs)
#extend __init__
self.fields['height'] =forms.CharField(widget=forms.TextInput(
attrs= {'size':site_id}))
height = forms.CharField()
or
someView()..
form = StylesForm(site_id = 1)
in forms.py
class StylesForm(forms.Form):
#overwrite __init__
def __init__(self,site_id):
# call standard __init__
super().__init__()
#extend __init__
self.fields['height'] =forms.CharField(widget=forms.TextInput(
attrs= {'size':site_id}))
height = forms.CharField()

Django 'QuerySet' object has no attribute 'split' using modelform

I'm having a problem getting my view to update a manytomany field. It returns this after the form is submitted.
Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/home/footbook/Ubuntu One/webapps/fb/poc/../poc/activity/views.py" in activity_save_page
44. group_names = form.cleaned_data['groups'].split()
Exception Type: AttributeError at /activity_save/
Exception Value: 'QuerySet' object has no attribute 'split'
Here are the files.
Models.py
class Group (models.Model):
group_nm = models.CharField(max_length=64)
group_desc = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
active_yn = models.BooleanField(default=True)
def __unicode__(self):
return self.group_nm
class Activity(models.Model):
activity_nm = models.CharField(max_length=60)
activity_desc = models.CharField(max_length=250)
startdt = models.DateField()
enddt = models.DateField()
crdt = models.DateTimeField(auto_now_add=True,editable=False)
groups = models.ManyToManyField(Group)
upddt = models.DateTimeField(editable=False)
def save(self, *args, **kwargs):
if not self.id:
self.crdt = datetime.date.today()
self.upddt = datetime.datetime.today()
super(Activity, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
forms.py
def make_custom_datefield(f):
formfield = f.formfield()
if isinstance(f, models.DateField):
formfield.widget.format = '%m/%d/%Y'
formfield.widget.attrs.update({'class':'datePicker', 'readonly':'true'})
return formfield
class ActivitySaveForm(forms.ModelForm):
formfield_callback = make_custom_datefield
def __init__(self, *args, **kwargs):
super(ActivitySaveForm, self).__init__(*args, **kwargs)
self.fields['activity_nm'].label = "Activity Name"
self.fields['activity_desc'].label = "Describe It"
self.fields['startdt'].label = "Start Date"
self.fields['enddt'].label = "End Date"
self.fields['groups'].label ="Group"
class Meta:
model = Activity
views.py
def activity_save_page(request):
if request.method == 'POST':
form = ActivitySaveForm(request.POST)
if form.is_valid():
act, created = Activity.objects.get_or_create(
activity_nm = form.cleaned_data['activity_nm']
)
act.activity_desc = form.cleaned_data['activity_desc']
if not created:
act.group_set.clear()
group_names = form.cleaned_data['groups'].split()
for group_name in group_names:
group, dummy = Group.objects.get_or_create(group_nm=group_name)
act.group_set.add(group)
act.save()
return HttpResponseRedirect('/activity/')
else:
form = ActivitySaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('activity_save.html', variables)
I think it would work if I wasn't using the modelform, but I need it to implement this datepicker. Since it's a manytomany field, I want to split them when they are entered into the database, but my queryset fails. I've tried changing this a bunch of different ways, but I'm stuck. I've seen a lot of similar questions, but they either had foreign keys or no modelform.
Thanks.
EDIT:
activity_save.html
{% extends "base.html" %}
{% block title %}Save Activity{% endblock %}
{% block head %}Save Activty{% endblock %}
<input class="datePicker" readonly="true" type="text" id="id_startdt" />
<input class="datePicker" readonly="true" type="text" id="id_enddt" />
{% block content %}
<form action="{% url activity.views.activity_save_page act_id%}" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="save it" />
</form>
{% endblock %}
Exactly as the error describes: a QuerySet does not have a split method. You cannot call my_qs.split().
form.cleaned_data['groups'] returns cleaned data; it has already taken care of the form string-to-python-object conversion for you, which in the case of a ManyToManyField is ultimately represented by a QuerySet in python.
A date field returns date objects, IntegerField an integer, CharFields a string, etc. in the same way via form cleaning.
If you want a list of group_names, you'd need to explicitly iterate through the objects in the QuerySet and pull their group_nm attribute.
group_names = [x.group_nm for x in form.cleaned_data['groups']]
I'm not sure you need to do all that in your view. You can directly save the form in the view without manually creating the objects and manipulating them.
Also, you need to get the id of activity so that you can update existing activity instance.
Update the urls.py to have these urls to have act_id:
url(r'^activity_app/save/(?P<act_id>\d+)/$', 'activity_app.views.activity_save_page'),
url(r'^activity_app/save/$', 'activity_app.views.activity_save_page'),
I would change the view to:
def activity_save_page(request, act_id=None):
act_inst = None
try:
if act_id:
act_inst = Activity.objects.get(id=act_id)
except Exception:
pass
if request.method == 'POST':
form = ActivitySaveForm(request.POST, instance=act_inst)
if form.is_valid():
return HttpResponseRedirect('/activity/')
else:
form = ActivitySaveForm(instance=act_inst)
variables = RequestContext(request, {
'form': form
})
return render_to_response('activity_save.html', variables)