Django Forms - Can't raise validation error in tests - django

I'm struggling to get my tests to throw a form validation error in Django. This is using standard/default input types.
# forms.py
class NewUserForm(forms.Form):
first_name = floppyforms.CharField(widget=floppyforms.TextInput(attrs={'class': 'form-control input-lg', 'placeholder': 'First Name'})),
last_name = floppyforms.CharField(widget=floppyforms.TextInput(attrs={'class': 'form-control input-lg', 'placeholder': 'Last Name'})),
email = forms.EmailField(),
mobile = floppyforms.CharField(
required=False,
widget=floppyforms.TextInput(attrs={'class': 'form-control input-lg', 'placeholder': 'Mobile number', 'autocomplete': 'false'})),
postcode = floppyforms.CharField(widget=floppyforms.TextInput(attrs={'class': 'form-control input-lg', 'placeholder': 'Postcode'})),
super_balance = floppyforms.CharField(widget=floppyforms.RangeInput(attrs={'class': 'bar', 'type': 'range', 'id': 'rangeinput',
'value': '492500', 'min': '75000', 'max': '1000000',
'step': '5000', }))
# tests.py
class NewUserFormTest(TestCase):
def setUp(self):
self.valid_data = {
'first_name': 'herp',
'last_name': 'derp',
'email': 'herp#derp.com',
'mobile': '0412345678',
'postcode': '00000',
'relationship_status': 'S',
'super_balance': '100000',
'current_super_provider': '49'
}
...
def test_invalid_fields(self):
form = NewUserForm({})
self.assertFalse(form.is_valid()) # correct
data = self.valid_data
data['email'] = 24234 # this field should fail
form = NewUserForm(data)
form.is_valid() # returns True
When I pass a blank dictionary to the initial form. form.errors displays {'super_balance': ['This field is required.']}. This is more confusing because the documentation states that unless explicitly declared then all fields are assumed to be required.
I'm using 1.8.5
Cheers in advance

You need to remove the trailing commas from all the fields in your form.
Instead of
class NewUserForm(forms.Form):
...
email = forms.EmailField(),
...
it should be
class NewUserForm(forms.Form):
...
email = forms.EmailField()
...
At the moment, NewUserForm.email is a tuple, not a field, so any values for that field in the data dictionary are ignored. The only field without the trailing comma is super_balance, which is why it is the only error that appears when you pass a blank dictionary to the form.

Related

Django - AttributeError 'str' object has no attribute 'court_number'

My edit view in my application won't update the court details whenever I want to edit it. I keep getting the error below. Adding, Viewing & Deleting functions are working okay. I cant find a solution on the other S/O answers. Some help on this would be appreciated. Thanks
Exception Value: 'str' object has no attribute 'court_number'
model.py
# Create your models here.
class Court(models.Model):
court_number = models.CharField(max_length=255, verbose_name='Court Number.')
accused_person = models.ForeignKey(AccusedPerson, on_delete=models.CASCADE, verbose_name='Accused Person')
court = models.CharField(choices=COURTS, max_length=255, verbose_name='Court')
court_verdict = models.CharField(choices=VERDICT, max_length=50, verbose_name='Court Status')
scheduled_on = models.DateField(verbose_name='Scheduled On')
created_by = models.ForeignKey(Profile, on_delete=models.CASCADE, verbose_name='Police Officer')
date_created = models.DateTimeField(auto_now_add=True, verbose_name='Date Created')
date_updated = models.DateTimeField(auto_now=True, verbose_name='Date Updated')
def __str__(self):
return str(self.court_number)
class Meta:
verbose_name_plural = 'Court'
forms.py
class EditCourtInfoForm(forms.Form):
court_number = forms.CharField(max_length=50, required=True, widget=forms.TextInput(attrs={'id': 'court_number', 'class': 'form-control mb-4', 'name': 'court_number', 'placeholder': 'Court Number'}))
accused_person = forms.ChoiceField(required=True, widget=forms.Select(attrs={'id': 'accused_person', 'class': 'form-control mb-4', 'name': 'accused_person', 'placeholder': 'Accused Person'}))
court = forms.ChoiceField(choices=COURTS, required=True, widget=forms.Select(attrs={'id': 'court', 'class': 'form-control mb-4', 'name': 'court', 'placeholder': 'Court'}))
court_verdict = forms.ChoiceField(choices=VERDICT, required=True, widget=forms.Select(attrs={'id': 'court_verdict', 'class': 'form-control mb-4', 'name': 'court_verdict', 'placeholder': 'Verdict'}))
scheduled_on = forms.DateField(required=True, widget=forms.DateInput(attrs={'type': 'date', 'id': 'scheduled_on', 'class': 'form-control mb-4', 'name': 'scheduled_on', 'placeholder': 'Scheduled On'}))
def __init__(self, *args, **kwargs):
super(EditCourtInfoForm, self).__init__(*args, **kwargs)
self.fields['accused_person'].choices = [(e.pk, f"{e.first_name}" + ' ' + f"{e.middle_name}" + ' ' + f"{e.last_name}") for e in AccusedPerson.objects.all()]
class Meta:
model = Court
fields = ['court_number', 'accused_person', 'court', 'court_verdict', 'scheduled_on']
views.py
def EditCourtInfo(request, id):
court = Court.objects.get(id=id)
if request.method == 'POST':
form = EditCourtInfoForm(request.POST)
if form.is_valid():
context = {'has_error': False}
court_number = form.cleaned_data['court_number']
accused_person = form.cleaned_data['accused_person']
court = form.cleaned_data['court']
court_verdict = form.cleaned_data['court_verdict']
scheduled_on = form.cleaned_data['scheduled_on']
print(scheduled_on)
court.court_number = court_number # The problem
court.accused_person = AccusedPerson.objects.get(pk=int(accused_person))
court.court = court
court.court_verdict = court_verdict
court.scheduled_on = scheduled_on
court.created_by = request.user.profile
if not context['has_error']:
court.save()
messages.success(request, '✅ Court Record Successfully Updated!')
return redirect('OfficerCourtInfo')
else:
messages.error(request, '⚠️ Court Record Was Not Updated!')
return redirect('EditCourtInfo', id=id)
else:
form = EditCourtInfoForm()
return render(request, 'Officer Edit Court.html', {'form':form, 'court':court})
The immediate problem is that you have
court = Court.objects.get(id=id)
but then later
court = form.cleaned_data['court']
So you are re-using a variable for a different purpose. You could fix this problem by using a different variable for one of these. However, you are making this much more complicated than you need to. The form will already take care of editing the Court object for you:
def EditCourtInfo(request, id):
court = Court.objects.get(id=id)
if request.method == 'POST':
form = EditCourtInfoForm(request.POST, instance=court). # pass the court object to the form
if form.is_valid():
form.save() # just save the form
messages.success(request, '✅ Court Record Successfully Updated!')
return redirect('OfficerCourtInfo')
else:
messages.error(request, '⚠️ Court Record Was Not Updated!')
return redirect('EditCourtInfo', id=id)
else:
form = EditCourtInfoForm()
return render(request, 'Officer Edit Court.html', {'form':form, 'court': court})
You will need to change your form to extend ModelForm instead of Form:
class EditCourtInfoForm(forms.ModelForm):
This code is untested, so I may have missed something. I suggest checking out the Django documentation to fill in any gaps in your understanding. You might even consider using a class-based view instead.

optional int(request.POST.get('timeout') throws error when empty

This field timeout = int(request.POST.get('timeout')) throws an error saying
invalid literal for int() with base 10: ''
this is my model field: timeout = models.IntegerField(default=10)
The forms submits just fine if I submit number because the form interprets it as a string but my form handler will convert it into integer. But it fails if I leave the field blank. Seems like it can't process an empty string.
What can I do ?
forms.py:
class TestCaseSuiteForm(forms.ModelForm):
name = forms.CharField(widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter Name'}), label='')
documentation = forms.CharField(widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter Documentation'}), label='')
setup = forms.CharField(widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter Setup'}), label='')
teardown = forms.CharField(widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter teardown'}), label='')
force_tags = forms.CharField(widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter Force Tags'}), label='')
timeout = forms.IntegerField(widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter Timeout (optional)'}),
required=False, label='')
class Meta:
model = TestCase
fields = [
'name',
'documentation',
'force_tags',
'setup',
'teardown',
'timeout',
]
my view:
def index(request):
if request.method == 'POST':
form_tc = TestCaseForm(request.POST)
form_ts = TestCaseSuiteForm(request.POST)
if form_tc.is_valid() or form_ts.is_valid():
form_tc.save()
form_ts.save()
return redirect('/list')
In case you're wondering ... I've got two forms using one submit button.
Having gone to the trouble of defining a form and validating it, you are supposed to use that validated data, rather than resorting to the raw post data. Not only will the validated data use defaults as defined in the form where necessary, it will also convert types etc.
if form_tc.is_valid() and form_ts.is_valid():
TestCase.objects.create(
name=form.cleaned_data['name'],
documentation=cleaned_data['documentation'],
...
)
Note, you need to use the unprefixed field names as the keys here.
But this still isn't really getting you what you want. You haven't defined all your model fields as form fields, so you won't get defaults for the fields you haven't defined. Instead you should be using a model form.
class TestCaseSuiteForm(forms.ModelForm):
class Meta:
model = TestCase
fields = ['name', 'documentation', ...]
and now in your view you simply save the form to create the objects:
if form_tc.is_valid() and form_ts.is_valid():
form_tc.save()
form_ts.save()
Now your model defaults will be used appropriately.
Set a default using:
timeout = int(request.POST.get('timeout', 0))

Use InlineForm for ForeignKey

With a ForeignKey relationship, is it possible display the form of the object being pointed to inline? The documentation shows how to do the reverse, with inline formsets; but I was unable to find how to replace the default selection box with an inline form.
For example, consider the case:
class Address(models.Model):
line_1 = models.CharField(max_length=100)
line_2 = models.CharField(max_length=100)
town = models.CharField(max_length=50)
state = models.ForeignKey(State,
on_delete=models.PROTECT)
post_code = models.CharField(max_length=4)
class Person(models.Model):
# ...
residential_address = models.ForeignKey(Address, unique=True)
postal_address = models.ForeignKey(Address, unique=True)
By default, the form for Person will display two drop-down selections: one for the residential address and one for the postal address. Could I display the form instead? Perhaps through a widget and/or custom field?
So, since there has been no answer yet, I've been working on making my own field and widgets. The display aspect (mostly) works fine now, but I get errors when I try and save the object as the referenced object needs to be created beforehand; however, I'm not sure if I can (or should?) save the object or not.
Here's what I have:
model.py
class Address(models.Model):
# as above
class AddressField(models.OneToOneField):
description = "An address"
def __init__(self, **kwargs):
"""The foreign key should always be to an Address class."""
kwargs['to'] = 'address.Address'
kwargs['related_name'] = '+'
super().__init__(**kwargs)
def formfield(self, **kwargs):
"""See https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.Field.formfield.
Change the default form field.
"""
from address.forms import AddressField as AddressFormField
defaults = dict(form_class=AddressFormField)
defaults.update(kwargs)
return super(AddressField, self).formfield(**defaults)
forms.py
class AddressField(fields.MultiValueField):
"""Address (multi-value) field."""
def __init__(self, *args, **kwargs):
error_messages = {
'incomplete': _("Please ensure all required fields are completed.")
}
sub_fields = (
# Unique key
models.ModelChoiceField(queryset=kwargs.pop('queryset'),
limit_choices_to=kwargs.pop('limit_choices_to'),
to_field_name=kwargs.pop('to_field_name'),
required=False,
error_messages={
'invalid': _("Invalid address.")
}),
# Line 1
fields.CharField(max_length=256,
error_messages={
'incomplete': _("Enter a first address line.")
}),
# Line 2
fields.CharField(max_length=256,
required=False),
# Town
fields.CharField(max_length=100,
error_messages={
'incomplete': _("Enter a town name.")
}),
# State
models.ModelChoiceField(queryset=State._default_manager.get_queryset(),
error_messages={
'incomplete': _("Select a state."),
'invalid': _("Invalid state.")
}),
# Post code
fields.CharField(max_length=4,
validators=[
RegexValidator(
regex=r"^[0-9]{4}$",
message=_("Invalid post code."),
code='invalid-postcode'
)
],
error_messages={
'incomplete': _("Enter a post code.")
}),
)
super().__init__(
error_messages=error_messages,
fields=sub_fields,
require_all_fields=False,
widget=AddressWidget,
*args, **kwargs
)
# TODO: Need to set the State choices
def compress(self, values):
"""See https://docs.djangoproject.com/en/1.10/ref/forms/fields/#django.forms.MultiValueField.compress.
Converts a list of values into an Address instance, unless no values
were given in which case None is returned.
"""
if not values:
return None
# If we have been given a PK, then fetch the object and updates its values
if values[0]:
try:
address = Address.objects.get(pk=values[0])
address.line_1 = values[1]
address.line_2 = values[2]
address.town = values[3]
address.state = values[4]
address.post_code = values[5]
except Address.DoesNotExist:
# TODO Handle this properly (if it every comes up)
raise Exception("Tried to get the address with key '{}', but could not find it.")
# Otherwise, instantiate a new Address
else:
address = Address(
line_1=values[1],
line_2=values[2],
town=values[3],
state=values[4],
post_code=values[5]
)
return address
class AddressWidget(widgets.MultiWidget):
"""Address widget."""
# TODO: Need ot handle the setting of choices (or not?)
choices = None
def __init__(self, *args, **kwargs):
sub_widgets = (
widgets.HiddenInput,
widgets.TextInput(attrs={
'title': _("Line 1"),
'size': 50,
'required': True,
'class': "address-line address-line1",
}),
widgets.TextInput(attrs={
'title': _("Line 2"),
'size': 50,
'required': False,
'class': "address-line address-line2",
}),
widgets.TextInput(attrs={
'title': _("Town"),
'size': 30,
'required': True,
'style': "flex-grow: 4;",
'class': "address-town",
}),
widgets.Select(attrs={
'title': _("State"),
'size': 3,
'required': True,
'style': "flex-grow: 1;",
'class': "address-state",
}),
widgets.TextInput(attrs={
'title': _("Post Code"),
'size': 4,
'required': True,
'style': "flex-grow: 1;",
'class': "address-postcode",
}),
)
super().__init__(
widgets=sub_widgets,
*args, **kwargs
)
self.widgets[4].choices=[[1, 2]]
print("widgets: {}".format(self.widgets))
def decompress(self, value):
"""See https://docs.djangoproject.com/en/1.10/ref/forms/widgets/#django.forms.MultiWidget.decompress.
Converts an Address object into a list of values; that is, performs the
converse of `compress` above.
"""
if not value:
return [None] * 6
if isinstance(value, Address):
return [
value.pk,
value.line_1,
value.line_2,
value.town,
value.state,
value.post_code
]
raise Exception("Unable to decompress the given value.")
def format_output(self, rendered_widgets):
"""See https://docs.djangoproject.com/en/1.10/ref/forms/widgets/#django.forms.MultiWidget.format_output.
Ensure that line 1 and 2 are on their own line, and place the town,
state and post code on the third line.
"""
print("choices: {}".format(self.choices))
return """
<div class="address-widget" style="display: flex; flex-direction: column;">
{pk}
{line_1}
{line_2}
<div class="address-locality" style="display: flex; flex-direction: row; flex-wrap: wrap">
{town}
{state}
{post_code}
</div>
</div>
""".format(
pk=rendered_widgets[0],
line_1=rendered_widgets[1],
line_2=rendered_widgets[2],
town=rendered_widgets[3],
state=rendered_widgets[4],
post_code=rendered_widgets[5],
)

How to save Foreign Key text input in Django model form

My view passes an id to my form. This id is a foreign key from another table. I am not able to save the id in the database table.
(id : voucher_id, table in which i am saving the form : TmpPlInvoicedet)
What i want to do
Send voucher_id from (View) to ---> TmpFormDetForm (Form) ---> TmpPlInvoicedet (DB)
Trying to get instance from the table 'TmpPlInvoice' (which has voucher_id as PK) and save it in the form gives me
DoesNotExist at /new/ TmpPlInvoice matching query does not exist
What am i doing wrong?
Views.py
def new_invoic(request):
# Create a voucher id according to my criteria
temp_vid = TmpPlInvoice.objects.order_by().values_list("voucher_id", flat=True).distinct()
if not temp_vid:
voucher_id = str(1).zfill(4)
else:
voucher_id = str(int(max(temp_vid)) + 1).zfill(4)
# POST METHOD TRying to show the voucher_id in the form in readonly format
if request.method == 'POST':
form_pk = TmpForm(request.POST or None, voucher_id=voucher_id,initial={'voucher_id': voucher_id})
if form.is_valid():
form_pk.save()
form = TmpFormDetForm(request.POST or None, voucher=voucher_id, initial={'voucher': voucher_id})
# My assumption is that since i have save the voucher_id in the TmpInvoice table so i can get the PK voucher_id value and save it in the TmpInvoiceDetForm
form.save()
return HttpResponseRedirect('/new/')
else:
return render_to_response('test.html',{'form': form, 'form_pk': form_pk},context_instance=RequestContext(request))
else:
form_pk = TmpForm(voucher_id=voucher_id,initial={'voucher_id': voucher_id})
form = TmpFormDetForm(voucher=voucher_id, initial={'voucher': voucher_id})
return render_to_response('test.html',{'form': form, 'form_pk': form_pk},context_instance=RequestContext(request))
Forms.py
# This form contains the FK. This one is giving errors while saving.
class TmpFormDetForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
voucher = kwargs.pop('voucher', None)
super(TmpFormDetForm, self).__init__(*args, **kwargs)
self.fields['voucher'].initial = TmpPlInvoice.objects.get(voucher_id=voucher)
voucher = forms.CharField(widget=forms.TextInput(attrs={'size':'40'}))
class Meta:
model = TmpPlInvoicedet
exclude = ['emp_id','particulars','qty', 'rate' , 'itemtot', 'stock_code' ]
widgets = {
'voucher': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '', 'required': 'False', 'name': 'voucher','readonly': 'readonly'}),
'lineitem': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Add Total', 'required': 'False', 'blank': 'True'})}
# This form takes the PK. I save the PK here first.
class TmpForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
voucher_id = kwargs.pop('voucher_id', None)
super(TmpFor, self).__init__(*args, **kwargs)
self.fields['voucher_id'].initial = voucher_id
pos_code = MyModelChoiceField(queryset=Positions.objects.all(), widget=forms.Select(attrs={'class': 'select2_single form-control', 'blank': 'True'}))
cust = MyModelChoiceField(queryset=Custodian.objects.all(), to_field_name='acct_id',widget=forms.Select(attrs={'class': 'select2_single form-control', 'blank': 'True'}))
acct = MyModelChoiceField(queryset=Item.objects.all(), to_field_name='stock_code',widget=forms.Select(attrs={'class':'select2_single form-control', 'blank': 'True'}))
voucher_date = forms.DateField(widget=forms.TextInput(attrs={'tabindex': '-1', 'class': 'form-control has-feedback-left', 'id': 'single_cal1','aria-describedby': 'inputSuccess2Status'}))
class Meta:
model = TmpPlInvoice
exclude = ['net_amt', 'post_date', 'address', 'posted']
widgets = {
'voucher_id': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '', 'required':'False', 'name': 'voucher_id', 'readonly': 'readonly'}),
'voucher_date': forms.TextInput(attrs={'tabindex': '-1', 'class': 'form-control has-feedback-left', 'id': 'single_cal1','aria-describedby': 'inputSuccess2Status'}),
'particulars': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Add Particulars', 'required':'False'}),
}
Models.py
class TmpPlInvoicedet(models.Model):
stock_code = models.CharField(max_length=13, blank=True, null=True)
voucher = models.ForeignKey(TmpPlInvoice, db_column='voucher_id')
lineitem = models.CharField(max_length=6)
particulars = models.CharField(max_length=200, blank=True, null=True)
qty = models.FloatField(blank=True, null=True)
rate = models.FloatField(blank=True, null=True)
itemtot = models.FloatField(blank=True, null=True)
emp_id = models.CharField(max_length=8, blank=True, null=True)
class Meta:
managed = False
db_table = 'tmp_pl_invoicedet'
unique_together = (('voucher', 'lineitem'),)
Easy peesy.
def master_detail(request):
def get_new_voucher_id():
temp_vid = TmpPlInvoice.objects.order_by().values_list("voucher_id", flat=True).distinct()
logger.info('Voucher ID already present %s', temp_vid)
if not temp_vid:
voucher_id = str(1).zfill(4)
else:
voucher_id = str(int(max(temp_vid)) + 1).zfill(4)
return voucher_id
voucher_id = get_new_voucher_id()
author_form = TmpForm(initial={'voucher_id': voucher_id})
author = TmpPlInvoice()
BookFormSet = inlineformset_factory(TmpPlInvoice, TmpPlInvoicedet, exclude=('emp_id', 'itemtot', 'voucher', 'lineitem','id'),
form=TmpFormDetForm, extra=1)
formset = BookFormSet(instance=author)
if request.method == 'POST':
logger.info('*'*50)
author = TmpForm(request.POST, initial={'voucher_id': voucher_id})
if author.is_valid():
logger.info('Data for Author is %s', author.cleaned_data)
created_author = author.save()
formset = BookFormSet(request.POST, instance=created_author)
if formset.is_valid():
logger.info('Data for Book is %s', formset.cleaned_data)
formset.save()
else:
logger.info('Formset errors %s', formset.errors)
else:
logger.info('Master form errors %s', author.errors)
logger.info('*'*50)
return HttpResponseRedirect('/new/')
else:
logger.info('Formset from GET is %s', formset.errors)
return render_to_response('new_invoice.html',
{'form': author_form, 'formset': formset},context_instance=RequestContext(request))
You seem to be creating a new invoice ID and then, in your form, attempting to get the invoice matching that ID. But that invoice doesn't exist yet, of course, because you haven't created it.
You might want to use get_or_create to ensure that the invoice is created if it doesn't exist.

Django forms validation fail

I have two fields in a form:
names_field = CharField(
label='Names',
widget=Textarea(attrs={'rows': '10', 'placeholder': 'input names here ...'}))
file_field = FileField(label='Upload from file')
Both are not required, but I can pass form.is_valid() only if I fill the both fields. It fails when I submit only one field names_field or file_field.
My view part:
form = AddNamessForm(request.POST, request.FILES)
if form.is_valid():
...
I thought that required=False is a default value but it's not true.
names_field = CharField(
label='Names',
reuired=False,
widget=Textarea(attrs={'rows': '10', 'placeholder': 'input names here ...'}))
file_field = FileField(label='Upload from file', reuired=False)