Validating blank django form field not working - django

I always use the following code to validate a form to prevent blank form submission. It always works in Django 1.8 but for some reason is not working in Django 2.2.
Here is the form
class CreateForm(forms.ModelForm):
class Meta:
model = Device
fields = ['category', 'item_name', 'quantity']
def clean_category(self):
category = self.cleaned_data.get('category')
if category == '':
raise forms.ValidationError('This field is required')
return category
def clean_item_name(self):
item_name = self.cleaned_data.get('item_name')
if item_name == '':
raise forms.ValidationError('This field is required')
return item_name
Here is the model
class Device(models.Model):
category = models.CharField(max_length=50, blank=True, null=True)
item_name = models.CharField(max_length=50, blank=True, null=True)
quantity = models.IntegerField(default='0', blank=False, null=True)
Thanks

I think the problem is that you did not check fro None, but nevertheless. I think you aim to do too much work yourself. You can just specify that the field is required=True [Django-doc], this will:
By default, each Field class assumes the value is required, so if you pass an empty value – either None or the empty string ("") – then clean() will raise a ValidationError exception.
So we can make the fields required with:
class CreateForm(forms.ModelForm):
category = forms.CharField(required=True, max_length=50)
item_name = forms.CharField(required=True, max_length=50)
class Meta:
model = Device
fields = ['category', 'item_name', 'quantity']
That being said, it is rather "odd" to specify blank=True [Django-doc] since this actually means that the field is not required in model forms. blank=True does not mean that the empty string is allowed, since even with blank=False, you can store empty strings in the field. A ModelForm will define (most of) its validation based on the model it "wraps", so that means that if you define the model better, you remove a lot of boilerplate code. Therefore I would advise eliminating blank=True.

Since you are specifying these fields as blank=True and null=True in your model so change those attributes
class Device(models.Model):
category = models.CharField(max_length=50, blank=False, null=False)
Or by default if you don't specify these blank and null attribute then it will be false by default so this should work also
class Device(models.Model):
category = models.CharField(max_length=50)
EDIT based on the comment:
Like the Willem said you need to check for None.You can do like this.
def clean_category(self):
category = self.cleaned_data.get('category')
if not category:
raise forms.ValidationError('This field is required')
return category

Related

Why django don't need all fields to test a model

I have a model like this:
class CreateDeal(models.Model):
name = models.CharField(max_length=100)
fuel = models.CharField(max_length=15)
mileage = models.PositiveIntegerField(db_index=True)
phone_number = models.CharField(max_length=17)
location = models.CharField(max_length=100, db_index=True)
car_picture = models.ImageField(upload_to='car_picture')
description = models.TextField()
price = models.PositiveSmallIntegerField(db_index=True)
available = models.BooleanField(default=True)
created_on = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
and I have a test class to test the model above like this:
class CreateDealTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username='alfa', email='alfa#hotmail.com', password='top_secret'
)
self.deal = CreateDeal.objects.create(
name='deal1', mileage=100, price=25, user=self.user
)
def test_deal_name(self):
deal = CreateDeal.objects.get(name='deal1')
expected_deal_name = f'{self.deal.name}'
self.assertAlmostEqual(expected_deal_name, str(deal))
if I run the test I have:
Ran 1 test in 0.166s
OK
My question is why django don't raise an exception since almost all fields in my model are required. And what I don't understand is if I remove one field of Createdeal in my setUp (like mileage, price, user or name) I have an error.
For instance if I remove mileage, I have this error:
raise utils.IntegrityError(*tuple(e.args))
django.db.utils.IntegrityError: (1048, "Column 'mileage' cannot be null")
Charfield, Imagefield and Textfield can be empty string which is valid at the database level, some of your fields have default values so they will be written if not set so that makes them also valid at the database level.
PositiveIntegerField and Foreign key cannot be set to empty string, just to value or null so they will fail since null=False by default.
The default blank=False option is only applied at the validation level, not at the database level. This means if you call full_clean() on your model, it will raise a ValidationError. But nothing stops you from saving an invalid model (save() does not call full_clean() as explained here).

Skip one or more constraints in Django form.is_valid()

Given the following model:
class User(models.Model):
role = models.IntegerField(default=0, blank=True)
name = models.CharField(max_length=255, blank=False, null=False)
email = models.EmailField(max_length=128, unique=True, blank=False, null=False)
I need that form.is_valid() would skip the unique constraint on email field.
It's essential, that emails would be unique, however in one particular view I wanna use get_or_create, which doesn't seem to work:
if form.is_valid():
usr, usr_created = models.User.objects.get_or_create(email=form.email)
<...>
Is this possible?
This is not possible.
unique=True creates a database constraint that checks for the uniqueness of the field. By its very design this disallows non-unique values, and the only way to circumvent this is to remove the constraint.
If the problem is that you want to allow multiple empty values, you can set null=True. Multiple NULL values are still considered unique by the constraint.
Don't list the field you want to override in the fields list and create a new custom field in the ModelForm:
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ["role", "name"]
email = forms.EmailField(max_length=128)
def save(self, commit=True):
instance = super(PatientForm, self).save(commit=False)
instance.email= self.cleaned_data.get("email")
return instance.save(commit)

Need to get id instead of selected value in modelchoicefield django

This is a very very basic question and i have already searched and tried lots of ways to do it but i want to know the good practice/best method to go about it.
There is a table in which i am trying to store the user selected code from another table. What is want is
A model form combo box which shows description field value while saves its respective pos_code in the table.
This is my model and forms:
pos_code = forms.ModelChoiceField(queryset=Positions.objects)
Here i want to insert the pos_code against the user selected description:
class TmpPlInvoice(models.Model):
voucher_id = models.CharField(primary_key=True, max_length=10)
pos_code = models.ForeignKey(Positions, models.DO_NOTHING, db_column='pos_code', blank=True, null=True)
class Meta:
managed = False
db_table = 'tmp_pl_invoice'
I'm getting the choice field from this model:
class Positions(models.Model):
pos_code = models.CharField(primary_key=True, max_length=10, blank=True, null=True)
description = models.CharField(max_length=100, blank=True, null=True)
class Meta:
managed = False
db_table = 'positions'
def __unicode__(self):
return self.description
But it gives me description instead of pos_code. I know that I am returning description but I need to show it to user and get code in the views.
Here is my full form
class TmpForm(forms.ModelForm):
description = forms.ModelChoiceField(queryset=Positions.objects.all())
class Meta:
model = TmpPlInvoice
exclude = ['net_amt', 'post_date', 'address', 'posted', 'voucher_date', 'particulars']
What i have
[IMG]http://i68.tinypic.com/zj89yx.jpg[/IMG]
Current form output
{'voucher_id': u'3452345', 'description': Positions: Premier Industrial Chemicals}
I can't use this 'description'. I need to save the code of Premier Industrial Chemicals in my TmpForm
What i need
[IMG]http://i66.tinypic.com/nh0x2a.jpg[/IMG]
Desired form output
{'voucher_id': u'3452345', 'pos_code': 0001}
This model form saved my life. The MyModelChoiceField class shows the label but send id on the backend.
class MyModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return obj.description
class TmpFormm(forms.ModelForm):
pos_code = MyModelChoiceField(queryset=Positions.objects.all(), widget=forms.Select(attrs={'class': 'select2_single form-control', 'blank': 'True'}))

null=True in model not accepting null values

views.py
report = Report.objects.get(user=user.id)
reportnotesform=ReportNotes(instance=report)
if request.method == 'POST':
locationnotesform=LocationNotes(request.POST,instance=report)
if locationnotesform.is_valid():
locationnotesform.save()
forms.py
class LocationNotes(forms.ModelForm):
other_location = forms.CharField(widget=forms.TextInput(attrs={'class':'ir-textbox'}))
location_description = forms.CharField(widget=forms.Textarea(attrs={'style':'width:20em'}))
models.py
class Report(models.Model):
user = models.ForeignKey(User, null=False)
location_description = models.TextField('Location description', null=True, blank=True)
other_location = models.CharField('Other', max_length=100, null=True, blank=True)
I am able to save the data,form is in update mode.
If i delete all data in field and click save,the field is not getting saved,means it is not taking null values.
Saving white space,but null is not saving.I want it to accept null values also.
Your model is fine but form would be giving you error.
Pass required=False to your field definitions in the form.
class LocationNotes(forms.ModelForm):
other_location = forms.CharField(required=False, widget=forms.TextInput(attrs={'class':'ir-textbox'}))
location_description = forms.CharField(required=False, widget=forms.Textarea(attrs={'style':'width:20em'}))
if i understand it correctly, In your LocationNotes form you need to make other_location and location_description optional also:
other_location = forms.CharField(
widget=forms.TextInput(attrs={'class':'ir-textbox'}),
required=False)

Limit values in the modelformset field

I've been trying to solve this problem for a couple of days now, getting quite desperate. See the commented out code snippets for some of the things I've tried but didn't work.
Problem: How can I limit the values in the category field of the IngredientForm to only those belonging to the currently logged in user?
views.py
#login_required
def apphome(request):
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
# Attempt #1 (not working; error: 'IngredientFormFormSet' object has no attribute 'fields')
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.none())
# ingrformset.fields['category'].queryset = Category.objects.filter(user=request.user)
# Attempt #2 (doesn't work)
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.filter(category__user_id = request.user.id))
models.py:
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
class Ingredient(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(Category, null=True, blank=True)
counter = models.IntegerField(default=0)
forms.py:
class IngredientForm(ModelForm):
class Meta:
model = Ingredient
fields = ('name', 'category')
UPDATE: I've made some progress but the solution is currently hard-coded and not really usable:
I found out I can control the categoryform field via form class and then pass the form in the view like this:
#forms.py
class IngredientForm(ModelForm):
category = forms.ModelChoiceField(queryset = Category.objects.filter(user_id = 1))
class Meta:
model = Ingredient
fields = ('name', 'category')
#views.py
IngrFormSet = modelformset_factory(Ingredient, form = IngredientForm, extra=1, fields=('name', 'category'))
The above produces the result I need but obviously the user is hardcoded. I need it to be dynamic (i.e. current user). I tried some solutions for accessing the request.user in forms.py but those didn't work.
Any ideas how to move forward?
You don't need any kind of custom forms. You can change the queryset of category field as:
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
IngrFormSet.form.base_fields['category'].queryset = Category.objects.filter(user__id=request.user.id)
Category.objects.filter(user=request.user)
returns a list object for the initial value in your form which makes little sense.
Try instead
Category.objects.get(user=request.user)
or
Category.objects.filter(user=request.user)[0]