How to validate form fields and display error message only for the missing field's value - django-views

In a form generating a list of instances I am passing values to two key fields for querying the related models. The said fields are mandatory (user required to fill both).
My form looks like this:
class striekerForm(forms.Form):
derStrieker = forms.ModelChoiceField(widget=forms.widgets.TextInput(attrs={'placeholder': 'type...', ...
demogrphie = forms.ModelChoiceField(widget=forms.widgets.TextInput(attrs={'placeholder': 'type...', ...
In the views.py I am doing:
class genDemo(formView):
template_name = ...
# ...
def post(self, request):
form = striekerForm
# ...
if form.is_valid():
# ...
context = {
'form': striekerForm(),
# ...
}
else:
messages.error(request, 'Both fields are required...')
form = striekerForm()
return HttpResponseRedirect(self.request.path_info)
return render(request, 'my_template.html', context)
Here what is happening is if any one of the fields is blank (not filled in by the user) I get error against both the fields (even when one of them might be filled in!!).
What I would want to do is to display error only for the field which has been left blank.
Can anybody pl suggest what I am doing wrong.

Related

How to add a Django form field dynamically depending on if the previous field was filled?

I have a Form (Formset) for users to update their profiles. This is a standard User model form, and custom Participants model form. Now, in cases when a participant provide his phone number, I need to refresh the whole Form with a new 'Code' filed dynamically. And the participant will type the code he received my SMS.
Here is how I am trying to do it:
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
form.save()
seller_form = SellerForm(self.request.POST, instance=self.object.seller)
if seller_form.is_valid():
seller = self.request.user.seller
seller.inn = seller_form.cleaned_data.get('inn')
if seller_form.cleaned_data.get('phone_number'):
seller_form.fields['code'] = models.CharField(max_length=4)
return render(request, self.template_name, {'form': form, 'seller_form': seller_form})
seller.save()
return HttpResponse('Seller updated')
return render(request, self.template_name, {'form': form, 'seller_form': seller_form})
Well I am not sure if this is the way I can add additional field. What would you suggest to handle this situation?
A technique I have used is to have an initially hidden field on the form. When the form otherwise becomes valid, I cause it to become visible, and then send the form around again. In class-based views and outline:
class SomeThingForm( forms.Modelform):
class Meta:
model=Thing
fields = [ ...
confirm = forms.BooleanField(
initial=False, required=False, widget=forms.widgets.HiddenInput,
label='Confirm your inputs?' )
class SomeView( CreateView): # or UpdateView
form_class = SomeThingForm
template_name = 'whatever'
def form_valid( self, form):
_warnings = []
# have a look at cleaned_data
# generate _warnings (a list of 2-tuples) about things which
# aren't automatically bad data but merit another look
if not form.cleaned_data['confirm'] and _warnings:
form.add_error('confirm', 'Correct possible typos and override if sure')
for field,error_text in _warnings:
form.add_error( field, error_text) # 'foo', 'Check me'
# make the confirm field visible
form.fields['confirm'].widget = forms.widgets.Select(
choices=((0, 'No'), (1, 'Yes')) )
# treat the form as invalid (which it now is!)
return self.form_invalid( form)
# OK it's come back with confirm=True
form.save() # save the model
return redirect( ...)
For this question, I think you would replace confirm with sms_challenge, a Charfield or IntegerField, initially hidden, with a default value that will never be a correct answer. When the rest of the form validates, form_valid() gets invoked, and then the same program flow, except you also emit the SMS to the phone number in cleaned_data.
_warnings = []
# retrieve sms_challenge that was sent
if form.cleaned_data['sms_challenge'] != sms_challenge:
_warnings.append( ['sms_challenge', 'Sorry, that's not right'] )
if _warnings:
...
form.fields['sms_challenge'].widget = forms.widgets.TextInput
return self.form_invalid( form)
I think that ought to work.

Looking for format for KeywordsField.save_form_data

I have a Mezzanine Project and am trying to update the keywords on a blog entry. I am having difficulty getting the format correct to call KeywordsField.save_form_data this invokes a js that will update the keywords on a blog post. See below:
From Messanine/generic/fields.py
class KeywordsField(BaseGenericRelation):
"""
Stores the keywords as a single string into the
``KEYWORDS_FIELD_NAME_string`` field for convenient access when
searching.
"""
default_related_model = "generic.AssignedKeyword"
fields = {"%s_string": CharField(editable=False, blank=True,
max_length=500)}
def __init__(self, *args, **kwargs):
"""
Mark the field as editable so that it can be specified in
admin class fieldsets and pass validation, and also so that
it shows up in the admin form.
"""
super(KeywordsField, self).__init__(*args, **kwargs)
self.editable = True
def formfield(self, **kwargs):
"""
Provide the custom form widget for the admin, since there
isn't a form field mapped to ``GenericRelation`` model fields.
"""
from mezzanine.generic.forms import KeywordsWidget
kwargs["widget"] = KeywordsWidget
return super(KeywordsField, self).formfield(**kwargs)
def save_form_data(self, instance, data):
"""
The ``KeywordsWidget`` field will return data as a string of
comma separated IDs for the ``Keyword`` model - convert these
into actual ``AssignedKeyword`` instances. Also delete
``Keyword`` instances if their last related ``AssignedKeyword``
instance is being removed.
"""
from mezzanine.generic.models import Keyword
related_manager = getattr(instance, self.name)
# Get a list of Keyword IDs being removed.
old_ids = [str(a.keyword_id) for a in related_manager.all()]
new_ids = data.split(",")
removed_ids = set(old_ids) - set(new_ids)
# Remove current AssignedKeyword instances.
related_manager.all().delete()
# Convert the data into AssignedKeyword instances.
if data:
data = [related_manager.create(keyword_id=i) for i in new_ids]
# Remove keywords that are no longer assigned to anything.
Keyword.objects.delete_unused(removed_ids)
super(KeywordsField, self).save_form_data(instance, data)
From my Views.py
class PubForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['keywords']
def UpdatePub(request, slug):
blog_post = BlogPost.objects.get(id=slug)
if request.method == 'POST':
form = PubForm(request.POST)
if form.is_valid():
publish_date = datetime.datetime.now()
blog_post.status = CONTENT_STATUS_PUBLISHED
publish_date=publish_date
tags=form.cleaned_data['keywords']
blog_post.save()
KeywordsField.save_form_data(user,blog_post,tags)
return HttpResponseRedirect('/write/')
else:
form = PubForm(instance=blog_post)
return render(request, 'blog_my_pub.html', {'form' : form})
It complains that the field 'user' has no attribute 'name'. I have tried many different values for this parameter and cannot figure it out. Any help would be appreciated.
Thanks for any input.

Django set Initial data in Forms

I am new to Django, Let say I have a view like follow with form
class DepartmentCreateView(LoginRequiredMixin,CreateView):
form_class = DepartmentForm
template_name = os.path.join(TEMPLATE_FOLDER, "create/index.html")
def get_success_url(self):
position_id = self.kwargs.get('position_id')
return reverse_lazy('departments_list', kwargs = { 'college_id' :
college_id })
def get_initial(self):
initial = super(DepartmentCreateView, self).get_initial()
initial.update({ 'college' : self.kwargs.get('college_id'),
'code' : 1 })
return initial
my form looks like
class DepartmentForm(forms.ModelForm):
class Meta:
model = Department
fields = ('name', 'college', 'code')
when I display form I am setting some default values to some fields(college) using get_initial(), it works as expected, but when I submit the form if the form is invalid(for example code is required but value is null) it shows error message as expected in the code field, but form fails to set default value to college again.
what mistake i made here?
You can use the initial= your_value for each field as found in Django Docs.
This should be in your forms.
OR
You can populate it key value pair like initial= {'your_key', your_value} when you init your form.
you can override the get_form to set the value of the input to your initial value
def get_form(self, form_class=None):
form = super(CreatePost, self).get_form(form_class)
form.fields['your_input'].widget.attrs.update({'value': 'new_value'})
return form
Django have a method get_initial_for_field for form, and in your case correct write that:
def get_initial_for_field(self, field, field_name):
"""
field_name: string = name of field in your model (college, code, name)
field = class of this field
return: value to initial, string, int or other (example: datetime.date.today())
"""
if field_name == 'college':
return self.kwargs.get('college_id')
elif field_name == 'code':
return '1'

django form is invalid, but no error messages

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

Django form empty numeric field clean validation

Im trying to validate in a django form if the user entered a numeric value on a field called "usd_value" using the clean method like this :
Form.py
class CostItemsForm(ModelForm):
def __init__(self, *args, **kwargs):
super(CostItemsForm, self).__init__(*args, **kwargs)
class Meta:
model = CostItems
fields = [
'group',
'description',
'usd_value',
'rer',
'pesos_value',
'supplier',
'position',
'observations',
'validity_date',
]
def clean_usd_value(self):
if self.cleaned_data.get('usd_value'):
try:
return int(self.cleaned_data['usd_value'].strip())
except ValueError:
raise ValidationError("usd_value must be numeric")
return 0
But is not working, i mean, if i leave the field empty or enter a text value there, alert doesn't activate at all and i got error (obviously) if i try to save the form. Any help ??
Here's my views.py
class CostItemInsert(View):
template_name='cost_control_app/home.html'
def post(self, request, *args, **kwargs):
if request.user.has_perm('cost_control_app.add_costitems'):
form_insert = CostItemsForm(request.POST)
if form_insert.is_valid():
form_save = form_insert.save(commit = False)
form_save.save(force_insert = True)
messages.success(request, "cost item created")
#return HttpResponseRedirect(reverse('cost_control_app:cost_item'))
else:
messages.error(request, "couldn't save the record")
return render(request, self.template_name,{
"form_cost_item":form_insert,
})
else:
messages.error(request, "you have no perrmissions to this action")
form_cost_item = CostItemsForm()
return render(request, self.template_name,{
"form_cost_item":form_cost_item,
})
I think your function name is wrong. Your field name is usd_value but your function is clean_usd. Change it to clean_usd_value and it should work.
Check Django doc section The clean_<fieldname>().
Edit
Also your return value for your clean method is wrong. Check the django doc example, you need to return the cleaned_data not 0:
def clean_usd_value(self):
cleaned_data = self.cleaned_data.get('usd_value'):
try:
int(cleaned_data)
except ValueError:
raise ValidationError("usd_value must be numeric")
return cleaned_data
But on a second throught, you might not even need the clean_usd_value method at all, django form field should have the default validation for you. Remove entirely the clean_usd_value method and see if it works.
I don't think you need custom validation for this. In fact, I think the builtin validation for django.forms.FloatField is going to be better than what you have.
Based on your error, I'm assuming that the form isn't using a FloatField for usd_value, and that's a bit odd. Make sure that your CostItems model has usd_value defined as a django.db.models.FloatField like below.
from django.db import models
class CostItems(models.Model):
usd_value = models.FloatField()
# other stuff
Once you do this, your CostItemsForm should automatically use django.forms.FloatField for usd_value. If it doesn't, you can always define this field explicitly.
from django import forms
class CostItemsForm(ModelForm):
usd_value = forms.FloatField(required=True)
class Meta:
model = CostItems
fields = [
'group',
'description',
'usd_value',
'rer',
'pesos_value',
'supplier',
'position',
'observations',
'validity_date',
]
If neither of these suggestions is helpful, please post your CostItems model.