Model field required on - django

I would like to change "required" property for field in my model clean() method.
Here's my model:
class SomeModel(models.Model):
type = models.CharField()
attr1 = models.ForeignKey(Attr1, blank=True, null=True)
attrs2 = models.ForeignKey(Attr2, blank=True, null=True)
Right now I am doing this in my ModelForm __init__ by adding a new parameter from view.
It dynamically sets required for fields.
Can I achieve the same in my models? I am using django-rest-framework for API (it's using a ModelForm) so full_clean() (that includes clean_fields() and clean()) will be run.
Say I would like attr1/attr2 fields required if type starts with some string.
I know I can do this check in Model.clean() but it will land into NON_FIELD_ERRORS then.
def clean(self):
if self.type.startswith("somestring"):
if self.attr1 is None and self.attr2 is None:
raise ValidationError("attr1 and attr2 are required..")
I would rather see these errors attached to attr1 and attr2 field errors with simple "This field is required" (standard "required" django error).

Here is a code example that work fine for me:
def clean(self):
is_current = self.cleaned_data.get('is_current',False)
if not is_current:
start_date = self.cleaned_data.get('start_date', False)
end_date = self.cleaned_data.get('end_date', False)
if start_date and end_date and start_date >= end_date:
self._errors['start_date'] = ValidationError(_('Start date should be before end date.')).messages
else:
self.cleaned_data['end_date']=None
return self.cleaned_data

Related

Django Single Field Validation cleaned_data

I am new in django and I am learning validation topics. Now I have a question about Single Field Validation and cleaned_data dictionary.I run django 3.2.
In my model.py there is this table:
class Personen(models.Model):
DEUTSCHLAND = 'DE'
SCHWEIZ = "CH"
ÖSTERREICH = "AT"
ENGLAND = "UK"
NATION_CHOICES = [
(DEUTSCHLAND, "Deutschland"),
(SCHWEIZ, "Schweiz"),
(ÖSTERREICH, "Österreich"),
(ENGLAND, "England"),
]
vorname = models.CharField(max_length=200)
nachname = models.CharField(max_length=200)
username = models.CharField(max_length=200)
stadt = models.CharField(max_length=15, validators=[
validate_nation_regexval], null=True)
nationalität = models.CharField(
max_length=2, choices=NATION_CHOICES, default=DEUTSCHLAND)
biere = models.ManyToManyField("Bier", through="PersonenBier")
def __str__(self):
return self.username
I import this in forms.py and I want to validate the field 'username'.
So I created a single field validation in forms.py
class PersonenForm(ModelForm):
class Meta:
model = Personen
fields = '__all__'
def clean_username(self):
print(self.cleaned_data)
username_passed = self.cleaned_data["username"]
username_req = "user_"
if not username_req in username_passed:
raise ValidationError("Ungültig")
return username_passed
So far everything works, but I am confused, as I expected, that the cleaned_data dict only includes the 'username' field. Why are there are also the 'vorname' and 'nachname' keys in the dict?
console output cleaned_data dict
Thank you for info.
cleaned_data calls the clean() method so it contains all the validated fields.
An excerpt from the docs:
The clean_() method is called on a form subclass – where is replaced with the name of the form field attribute. This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is. This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
you can read form validation steps in the above link I shared.

Django ModelForm: change a field value when blank

Is there any way to change a field value (related to a foreign key) in a Django ModelForm once the form is initialized and filled by the user (I'm using request.POST). I want to change the value when the user doesn't select any option of the dropdown list. I tried this formulari_mostra.data['pools'] = 1 in views.py after saving the feedback from the form with no result:
def sample_form(request):
formulari_mostra=FormulariMostra()
if request.method=="POST":
formulari_mostra=FormulariMostra(request.POST or None)
if formulari_mostra.is_valid():
feedback = formulari_mostra.save(commit=False)
sample = Sample.objects.all()
feedback.sample = sample
feedback.save()
formulari_mostra.save_m2m()
formulari_mostra.data['pools'] = 1
messages.success(request, 'Mostra enregistrada correctament!')
return render(request, "sample/formulari_mostra.html", {'formulari':formulari_mostra})
I got this message:
This QueryDict instance is immutable
I know I can set an initial (default) before introducing data in the form but I don't want to have the default option highlighted in the dropdown.
My model:
class Sample(models.Model):
id_sample = models.AutoField(primary_key=True)
name = models.CharField(unique=True, max_length=20)
sample_id_sex = models.ForeignKey(Sex, on_delete=models.CASCADE, db_column='id_sex', verbose_name='Sexe')
indexes = models.ManyToManyField(Index, through='SamplePoolIndexCand', through_fields=('sample_id', 'index_id'), blank=True, verbose_name="Índexs")
pools = models.ManyToManyField(Pool, through='SamplePoolIndexCand', through_fields=('sample_id', 'pool_id'), blank=True, verbose_name="Pools")
gene_cand_lists = models.ManyToManyField(GeneCandList, through='SamplePoolIndexCand', through_fields=('sample_id', 'gene_cand_list_id'), blank=True, verbose_name="Llista de gens candidats")
class Meta:
db_table = 'sample'
def __str__(self):
return self.name
My forms.py:
class FormulariMostra(ModelForm):
class Meta:
model = Sample
fields = ("name", "sample_id_sex", "pools",)
first - you are setting the polls property after save() is called, so even if this would work, you are not saving the change.
second - if you want to set the polls property to model, then set it to the model instead of the form (formulari_mostra). I dont know how your models look like, so I can only assume the model which has the pools property is in the variable feedback, so you want to do:
feedback = formulari_mostra.save(commit=False)
feedback.pools = 1
feedback.save()

Validating blank django form field not working

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

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).

How to hide model field in Django Admin?

I generate field automaticly, so I want to hide it from user. I've tried editable = False and hide it from exclude = ('field',). All this things hide this field from me, but made it empty so I've got error: null value in column "date" violates not-null constraint.
models.py:
class Message(models.Model):
title = models.CharField(max_length = 100)
text = models.TextField()
date = models.DateTimeField(editable=False)
user = models.ForeignKey(User, null = True, blank = True)
main_category = models.ForeignKey(MainCategory)
sub_category = models.ForeignKey(SubCategory)
groups = models.ManyToManyField(Group)`
admin.py:
class MessageAdminForm(forms.ModelForm):
def __init__(self, *arg, **kwargs):
super(MessageAdminForm, self).__init__(*arg, **kwargs)
self.initial['date'] = datetime.now()
class MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
list_display = ('title','user',)
list_filter = ('date',)
Based on your model setup, I think the easiest thing to do would change your date field to:
date = models.DateTimeField(auto_now=True)
that should accomplish what you're after and you don't even need to exclude it from the admin, it's excluded by default. If you have auto_now=True it will act as a 'last update time'. If you have auto_now_add=True it will act as a creation time stamp.
There are several other ways you could accomplish your goal if your use case is more complex than a simple auto date field.
Override the model's save method to put the value in.
class Message(models.Model):
title=models.CharField(max_length=100)
date = models.DateTimeField(editable=False)
def save(*args, **kwargs):
self.date = datetime.datetime.now()
super(Message, self).save(*args, **kwargs)
What you are trying to do with the Model Admin isn't quite working because by default django only transfers the form fields back to a model instance if the fields are included. I think this might be so the model form doesn't try to assign arbitrary attributes to the model. The correct way to accomplish this would be to set the value on the instance in your form's save method.
class MessageAdminForm(forms.ModelForm):
def save(*args, **kwargs):
self.instance.date = datetime.now()
return super(MessageAdminForm, self).save(*args, **kwargs)