I am trying to clean my inline formset data. I have three rules: 1) must supply at least one name, 2) if you specify a name you must specify both a first and last, and 3) no spaces. When the data saves, the spaces are still saved to the database. What am I doing wrong?
Another issue is that the Validation Errors are not displayed. Why is that?
forms.py:
class UserNameForm(forms.models.BaseInlineFormSet):
def clean(self):
super(UserNameForm, self).clean()
count = 0
for form in self.forms:
try:
firstName = form.cleaned_data.get("name_first")
middleName = form.cleaned_data.get("name_middle")
lastName = form.cleaned_data.get("name_last")
if firstName or middleName or lastName:
if len(firstName) == 0 or len(lastName) == 0:
raise forms.ValidationError("First and Last name are required.")
if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
count += 1
form.cleaned_data['name_first'] = firstName.replace(" ",'')
form.cleaned_data["name_middle"] = middleName.replace(" ",'')
form.cleaned_data["name_last"] = lastName.replace(" ",'')
except AttributeError:
pass
if count < 1:
raise forms.ValidationError('You must have at least one order')
views.py
UserNameFormSet = inlineformset_factory(Profile, Alias, formset=UserNameForm, extra=2, can_delete=True )
if request.method == 'POST':
cNameFormSet = UserNameFormSet(request.POST, request.FILES, instance=cProfile)
if cNameFormSet.is_valid():
testResults = cNameFormSet.save(commit=False)
for form in testResults:
form.save()
You can apply both a form and formset. Use the formset to validate data across the forms and the form to validate data within the form and raise validation errors.
UserNameFormSet = inlineformset_factory(Profile,
Alias,
form=UserNameForm,
formset=AtLeastOne,
extra=2,
can_delete=True )
class AtLeastOne(forms.models.BaseInlineFormSet):
def clean(self):
count = 0
for form in self.forms:
try:
if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
count += 1
except AttributeError:
pass
if count < 1:
raise forms.ValidationError('You must have at least one name')
class UserNameForm(forms.ModelForm):
class Meta:
model = Alias
def clean(self):
cleaned_data = self.cleaned_data
# Check 1: Must have first and last.
firstName = cleaned_data.get("name_first")
middleName = cleaned_data.get("name_middle")
lastName = cleaned_data.get("name_last")
if firstName or middleName or lastName:
if len(firstName) == 0 or len(lastName) == 0:
raise forms.ValidationError("First and Last name are required.")
# Check 2: No spaces.
cleaned_data["name_first"] = firstName.replace(" ",'')
cleaned_data["name_middle"] = middleName.replace(" ",'')
cleaned_data["name_last"] = lastName.replace(" ",'')
return cleaned_data
Related
Can anyone tell me what's wrong with my code? I am trying to use filter but its showing ''QuerySet' object has no attribute 'enter_the_destination_account_number'. I tried get() but it shows,
get() returned more than one MoneyTransfer -- it returned 14!.
here's some snap of code. Thanks in advance
models.py
class Status (models.Model):
user_name = models.CharField(max_length=150, default=None)
account_number = models.IntegerField()
balance = models.IntegerField()
phone_number= models.CharField(max_length=20, default=0)
class MoneyTransfer(models.Model):
enter_your_user_name = models.CharField(max_length = 150, default = None)
enter_the_destination_account_number = models.IntegerField()
enter_the_destination_phone_number=models.CharField(max_length=20, default=None)
enter_the_amount_to_be_transferred_in_INR = models.IntegerField()
views.py
def TransferMoney(request):
if request.method == "POST":
form = forms.MoneyTransferForm(request.POST)
if form.is_valid():
form.save()
curr_user = models.MoneyTransfer.objects.filter(enter_your_user_name=request.user)
dest_user_acc_num = curr_user.enter_the_destination_account_number #dest_phone number add korte hobe
dest_phone_num= curr_user.enter_the_destination_phone_number
temp = curr_user # NOTE: Delete this instance once money transfer is done
dest_user = models.Status.objects.get(account_number=dest_user_acc_num) # FIELD 1
dest_phn= models.Status.objects.get(phone_number= dest_phone_num)
transfer_amount = curr_user.enter_the_amount_to_be_transferred_in_INR # FIELD 2
curr_user = models.Status.objects.get(user_name=request.user) # FIELD 3
# Now transfer the money!
curr_user.balance = curr_user.balance - transfer_amount
#dest_phn.balance = dest_phn.balance + transfer_amount
dest_user.balance = dest_user.balance + transfer_amount
# Save the changes before redirecting
curr_user.save()
dest_user.save()
temp.delete() # NOTE: Now deleting the instance for future money transactions
return redirect(index)
else:
form = forms.MoneyTransferForm()
return render(request, "epayapp/Transfer_money.html", {"form": form})
The issue is that filter returns a queryset, not an object.
So you cannot access the fields directly from the queryset, what you can do is grab the first element from the queryset and perform the operations you want.
Like this: curr_user = models.MoneyTransfer.objects.filter(enter_your_user_name=request.user).first()
So it'll look like this:
def TransferMoney(request):
if request.method == "POST":
form = forms.MoneyTransferForm(request.POST)
if form.is_valid():
form.save()
# Grab the first object from the queryset
curr_user = models.MoneyTransfer.objects.filter(enter_your_user_name=request.user).first()
dest_user_acc_num = curr_user.enter_the_destination_account_number #dest_phone number add korte hobe
dest_phone_num= curr_user.enter_the_destination_phone_number
temp = curr_user # NOTE: Delete this instance once money transfer is done
dest_user = models.Status.objects.get(account_number=dest_user_acc_num) # FIELD 1
dest_phn= models.Status.objects.get(phone_number= dest_phone_num)
transfer_amount = curr_user.enter_the_amount_to_be_transferred_in_INR # FIELD 2
curr_user = models.Status.objects.get(user_name=request.user) # FIELD 3
# Now transfer the money!
curr_user.balance = curr_user.balance - transfer_amount
#dest_phn.balance = dest_phn.balance + transfer_amount
dest_user.balance = dest_user.balance + transfer_amount
# Save the changes before redirecting
curr_user.save()
dest_user.save()
temp.delete() # NOTE: Now deleting the instance for future money transactions
return redirect(index)
else:
form = forms.MoneyTransferForm()
return render(request, "epayapp/Transfer_money.html", {"form": form})
the filter method on objects does not returned an object as you think. it will return a Queryset object
You have to loop into the results of filter or take an item with first() function for example
def TransferMoney(request):
if request.method == "POST":
form = forms.MoneyTransferForm(request.POST)
if form.is_valid():
form.save()
queryset = models.MoneyTransfer.objects.filter(enter_your_user_name=request.user)
curr_user = queryset.first()
dest_user_acc_num = curr_user.enter_the_destination_account_number #dest_phone number add korte hobe
dest_phone_num= curr_user.enter_the_destination_phone_number
temp = curr_user # NOTE: Delete this instance once money transfer is done
dest_user = models.Status.objects.get(account_number=dest_user_acc_num) # FIELD 1
dest_phn= models.Status.objects.get(phone_number= dest_phone_num)
transfer_amount = curr_user.enter_the_amount_to_be_transferred_in_INR # FIELD 2
curr_user = models.Status.objects.get(user_name=request.user) # FIELD 3
# Now transfer the money!
curr_user.balance = curr_user.balance - transfer_amount
#dest_phn.balance = dest_phn.balance + transfer_amount
dest_user.balance = dest_user.balance + transfer_amount
# Save the changes before redirecting
curr_user.save()
dest_user.save()
temp.delete() # NOTE: Now deleting the instance for future money transactions
return redirect(index)
else:
form = forms.MoneyTransferForm()
return render(request, "epayapp/Transfer_money.html", {"form": form})
get function have to returned only one element or it will raises a exception...
Maybe set account_number and user_name unique in your fields definition for rpeventing this cases
I'm having to do some validation across both a form and formset. The £££ amount in the form must equal the sum of the amounts in the formset.
After a lot of Googling I found a solution where I add a custom init to the baseformset as follows:
class BaseSplitPaymentLineItemFormSet(BaseFormSet):
def __init__(self, cr=None, *args, **kwargs):
self._cr = cr
super().__init__(*args, **kwargs)
def clean(self):
if any(self.errors):
return
sum_dr = 0
for form in self.forms:
sum_dr += form.cleaned_data.get('dr')
if sum_dr != float(self._cr):
raise forms.ValidationError('The amount entered needs to equal the sum of the split payments.')
I then pass the amount value from the form when the formset is instantiated, so that the value can be used in the formset validation:
lineitem_formset = LineItemFormSet(form.data['amount'], request.POST)
This worked great for the create_new view which uses formset_factory(). This morning I wrote the update view using inline_formsetfactory(), but I now get an error:
__init__() got an unexpected keyword argument 'instance'
I only have a basic understanding of how the custom init works, so I can't find a solution to this error.
Forms.py:
class SplitPaymentForm(forms.Form):
date = forms.DateField(widget=DateTypeInput())
account = GroupedModelChoiceField(queryset=Ledger.objects.filter(coa_sub_group__type='a').order_by('coa_sub_group__name','name'), choices_groupby = 'coa_sub_group')
store = forms.CharField(required=True)
amount = forms.DecimalField(decimal_places=2)
class SplitPaymentLineItemForm(ModelForm):
ledger = GroupedModelChoiceField(queryset=Ledger.objects.all().order_by('coa_sub_group__name', 'name'), choices_groupby = 'coa_sub_group', empty_label="Ledger", required=True)
project = forms.ModelChoiceField(queryset=Project.objects.filter(status=0), empty_label="Project", required=False)
class Meta:
model = LineItem
fields = ['description','project', 'ledger','dr',]
# This init disallows empty formsets
def __init__(self, *arg, **kwarg):
super(SplitPaymentLineItemForm, self).__init__(*arg, **kwarg)
self.empty_permitted = False
class BaseSplitPaymentLineItemFormSet(BaseFormSet):
def __init__(self, cr=None, *args, **kwargs):
self._cr = cr
super().__init__(*args, **kwargs)
def clean(self):
if any(self.errors):
return
sum_dr = 0
for form in self.forms:
sum_dr += form.cleaned_data.get('dr')
if sum_dr != float(self._cr):
raise forms.ValidationError('The amount entered needs to equal the sum of the split payments.')
Views.py:
def split_payments_new(request):
LineItemFormSet = formset_factory(SplitPaymentLineItemForm, formset=BaseSplitPaymentLineItemFormSet, extra=2)
if request.method == 'POST':
form = SplitPaymentForm(request.POST)
lineitem_formset = LineItemFormSet(form.data['amount'], request.POST)
if form.is_valid() and lineitem_formset.is_valid():
q0 = JournalEntry(user=request.user, date=form.cleaned_data['date'], type="SP",)
q1 = LineItem(journal_entry=q0, description=form.cleaned_data['store'], ledger=form.cleaned_data['account'], cr=form.cleaned_data['amount'])
q0.save()
q1.save()
for lineitem in lineitem_formset:
q2 = LineItem(journal_entry=q0,description=lineitem.cleaned_data.get('description'),ledger=lineitem.cleaned_data.get('ledger'),project=lineitem.cleaned_data.get('project'),dr=lineitem.cleaned_data.get('dr'))
q2.save()
messages.success(request, "Split payment successfully created.")
return HttpResponseRedirect(reverse('journal:split_payments_show_detail', kwargs={'pk': q0.id}) )
else:
form = SplitPaymentForm(initial = {'date': datetime.date.today().strftime('%Y-%m-%d')})
lineitem_formset = LineItemFormSet()
return render(request, 'journal/split_payments_new.html', {'form': form, 'formset': lineitem_formset})
def split_payments_update(request, pk):
journal_entry = get_object_or_404(JournalEntry, pk=pk, type="SP")
lineitem = LineItem.objects.get(journal_entry=journal_entry.id, dr__isnull=True)
initial = {
'date': journal_entry.date.strftime('%Y-%m-%d'),
'account': lineitem.ledger,
'store': lineitem.description,
'amount': lineitem.cr,
}
form = SplitPaymentForm(initial=initial)
LineItemFormSet = inlineformset_factory(JournalEntry, LineItem, form=SplitPaymentLineItemForm, formset=BaseSplitPaymentLineItemFormSet, extra=0)
lineitem_formset = LineItemFormSet(instance=journal_entry)
if request.method == 'POST':
lineitem_formset = LineItemFormSet(form.data['amount'], request.POST, instance=journal_entry)
form = SplitPaymentForm(request.POST)
if lineitem_formset.is_valid() and form.is_valid():
lineitem_formset.save()
journal_entry.date = form.cleaned_data['date']
lineitem.ledger = form.cleaned_data['account']
lineitem.description = form.cleaned_data['store']
lineitem.cr = form.cleaned_data['amount']
journal_entry.save()
lineitem.save()
messages.success(request, "Split payment successfully updated.")
return HttpResponseRedirect(reverse('journal:split_payments_show_detail', kwargs={'pk': journal_entry.id}) )
return render(request, 'journal/split_payments_update.html',{'form': form, 'formset': lineitem_formset, 'journal_entry': journal_entry})
Solved. Just had to use BaseInlineFormSet.
I am quite new to the Django testing software. Right at the moment I am trying to create a test class for validators connected with given form ( process of cleaning the input data):
forms.py:
class SignupForm(forms.Form):
email = forms.EmailField(max_length=200)
first_name = forms.CharField(max_length=200)
last_name = forms.CharField(max_length=200)
company_name = forms.CharField(max_length=200)
password1 = forms.CharField(max_length=200)
password2 = forms.CharField(max_length=200)
def clean(self):
cleaned_data = self.cleaned_data
cleaned_username_obtained = cleaned_data.get('username')
if(cleaned_username_obtained != None):
username_obtained = cleaned_username_obtained.lower()
else:
raise ValidationError('Username is empty!')
cleaned_email_address = cleaned_data.get('email')
cleaned_password1 = cleaned_data.get('password1')
cleaned_password2 = cleaned_data.get('password2')
cleaned_first_name = cleaned_data.get('first_name')
cleaned_last_name = cleaned_data.get('last_name')
cleaned_company_name = cleaned_data.get('company_name')
if username_obtained != None:
if((User.objects.filter(username=username_obtained)).count() > 0):
self._errors['username'] = [u'Username is already in use']
elif((ModelOfAdvertisementClient.objects.filter(email_address=cleaned_email_address)).count() > 0):
self._errors['email'] = [u'Email is already in use']
elif(len(cleaned_password1) == 0):
self._errors['password1'] = [u'Password input box is empty']
elif(cleaned_password2 != cleaned_password1):
self._errors['password2'] = [u'Inserted passwords are different!']
return cleaned_data
test_forms.py:
class SignupFormTest(TestCase):
#classmethod
def test_correct_values(self):
form = SignupForm({
'username': 'testUsername',
'email': 'mark12#gmail.com',
'first_name': 'Karl',
'last_name': 'Smith',
'company_name': 'HiTech Inc.',
'password1': 'test123',
'password2': 'test123'
}
)
print(str(form.is_valid()))
self.assertFalse(form.is_valid())
Unfortunately, each time I am launching written above code, it ends up with following error message:
self.assertFalse(form.is_valid())
TypeError: assertFalse() missing 1 required positional argument: 'expr'
and due to the fact, that
form.is_valid()
returns "False", I literally have no clue of what is wrong with this assert
Testing methods should not have #classmethod decorators. Those are meant for class-wide setup/teardown code. In your case because of #classmethod decorator you are silently calling an unbound assertFalse() method that expects 2 arguments: a TestCase instance an a boolean expression.
I have a problem with a submit form when I want to save the profile ID the form have an error, i dont understand why because in the console all is ok but the form_valis is false, so think because the ModelChoiseField send a pk in sting format so how can i convert the string pk to int pk ?
My Form
class UsuarioForm(forms.ModelForm):
id_perfil = forms.ModelChoiceField(queryset=Perfil.objects.filter(status='1'), label="Perfil" ,empty_label="Seleciona perfil", widget=forms.Select(attrs={'class':'form-control'}))
My Models
class Usuario(models.Model):
id_usuario = models.AutoField(primary_key=True)
nombre = models.CharField(max_length=255)
id_perfil = models.IntegerField()
status = models.CharField(max_length=50)
class Perfil(models.Model):
id_perfil = models.AutoField(primary_key=True)
nombre = models.CharField(max_length=255)
status = models.CharField(max_length=50)
The save method
def save_usuario_form(request, form, template_name):
data = dict()
if request.method == 'POST':
if form.is_valid():
usuario = form.save(commit=False)
if usuario.status == '':
usuario.status = '1'
usuario.id_usuario_alt = '1'
elif usuario.status == '1':
usuario.status = '2'
form.save()
data['form_is_valid']= True
usuarios = Usuario.objects.filter(status='1').order_by('id_usuario')[:5]
data['html_usuario_list'] = render_to_string('back/Modulo_usuarios/usuarios_list.html',{
'usuarios':usuarios
})
else:
data['form_is_valid']= False
context = {'form':form}
data['html_form'] = render_to_string(template_name, context, request=request)
return JsonResponse(data)
The error
All fields are fill and post method is OK
views.py
#app.route('/new', methods = ['POST', 'GET'])
#login_required
def new():
form = StudentForm()
if request.method == 'POST':
if form.validate_on_submit():
flash('All fields are required.')
return render_template('form.html', action = 'new', form = form)
else:
student = students(
request.form['first_name'], request.form['last_name'], \
request.form['date_of_birth'], \
request.form['date_of_join'], request.form['address']
db.session.add(student)
db.session.commit()
return redirect(url_for('show_all'))
return render_template('form.html', action = url_for('new'), form = form)
#app.route('/edit/<int:id>', methods = ['POST', 'GET'])
#login_required
def edit(id):
item = students.query.get(id)
form = StudentForm(obj=item)
if request.method == 'POST':
if form.validate_on_submit():
item = students.query.get(id)
form = StudentForm(obj=item)
return render_template('form.html', action = url_for('edit',id = id), form = form)
else:
form.populate_obj(item)
db.session.add(item)
db.session.commit()
return redirect(url_for('show_all'))
return render_template('form.html', action = url_for('edit',id = id), form = form)
models.py
class students(db.Model):
__tablename__ = "students"
id = db.Column('id', db.Integer, primary_key = True)
first_name = db.Column(db.String(25))
last_name = db.Column(db.String(25))
date_of_birth = db.Column(db.Date)
date_of_join = db.Column(db.Date)
address = db.Column(db.String(200))
forms.py
class StudentForm(Form):
first_name = TextField("First Name:")
last_name = TextField("Last Name:")
date_of_birth = DateField("Date of Birth:", format='%m/%d/%Y')
date_of_join = DateField("Date of Joining:", format='%m/%d-%Y')
address = TextAreaField("Address:")
submit = SubmitField("Submit")
All other fields are added to the database while adding new item, but the date is not stored. I cant find what the problem actually is. The date field is stored during edit if i use form.validate() for form validation. And if i use form.validate_on_submit() the date field is not stored while adding or editing...