Django form doesn't get the value in clean method - django

I'm trying to write a clean method form one of my forms in Django 1.5.
The user has the ability to chose from a list of pre existing objects or inserting one by himself, but he have to do one of this two things (if he do both the ModelChoiceField has precedence in the relative view).
This is the incriminated form's part:
class UploadFileForm(forms.Form):
subject = forms.ModelChoiceField(queryset=Subject.objects.all(), required=False, label='What subject this file is about?')
subject1 = forms.CharField(required=False, label="Or insert your subject if you didn't find it in the menu")
def clean_subject(self):
subject = self.cleaned_data.get('subject')
subject1 = self.cleaned_data.get('subject1')
if not subject and not subject1:
raise forms.ValidationError('This field is required.')
whitch is relative to this model:
class Subject(models.Model):
subject = models.CharField(max_length = 30, primary_key=True, blank=False, null=False)
problem is: if the user let the ModelChoiceField empty and tries to insert a value in the CharField the form raises the error anyway (and of course it shouldn't).
I also tryed: subject1 = self.cleaned_data.get('subject1', None) but in this case the subject1 value will always be None (so the problem it's basically the same).
I'm really going crazy trying to undestrand why.

You should use clean method instead of clean_<fieldname> to validate fields that depend on each other (Django clean() docs).
class UploadFileForm(form.Form):
subject = forms.ModelChoiceField(queryset=Subject.objects.all(), required=False)
subject1 = forms.CharField(required=False)
def clean(self):
subject = self.cleaned_data.get('subject')
subject1 = self.cleaned_data.get('subject1')
if not subject and not subject1:
raise forms.ValidationError('Subject field is required.')
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.

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

Django - If one model field exists, another field must be empty/inaccessible

I have a model called Team. Team has one User as team_leader and can have multiple team_member's.
Under User, I added the following fields:
class User(AbstractUser):
team_leader = models.OneToOneField(Team, null=True, blank=True)
team_member = models.ForeignKey(Team, null=True, blank=True)
Obviously a User shouldn't be a team_leader and team_member at the same time. I have set both fields as null/blank so I can always just leave one of them blank. However for extra precaution I'd like to completely disable one of these fields when the other field is activated.
I'm aware of the disabled function, but it is only for disabling the field on a form. I want to eliminate any chance of tampering by completely disabling one model field.
Is there a way to do this?
class UserForm(ModelForm):
def clean(self):
cleaned_data = super(UserForm, self).clean()
team_leader = cleaned_data.get('team_leader', None)
team_member = cleaned_data.get('team_member', None)
if team_leader and len(team_member)!=0:
if team_leader in team_member:
raise forms.ValidationError("team_leader can't in team_member")
class Meta:
model = User
fields = ['team_leader', 'team_member']
class UserAdmin(admin.ModelAdmin):
form = UserForm

Model field required on

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

Models unique_together constraint + None = fail?

2 questions:
How can I stop duplicates from being created when parent=None and name is the same?
Can i call a model method from within the form?
Please see full details below:
models.py
class MyTest(models.Model):
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=255, blank=True, unique=True)
owner = models.ForeignKey(User, null=True)
class Meta:
unique_together = ("parent", "name")
def save(self, *args, **kwargs):
self.slug = self.make_slug()
super(MyTest, self).save(*args, **kwargs)
def make_slug(self):
# some stuff here
return generated_slug
note: slug = unique as well!
forms.py
class MyTestForm(forms.ModelForm):
class Meta:
model = MyTest
exclude = ('slug',)
def clean_name(self):
name = self.cleaned_data.get("name")
parent = self.cleaned_data.get("parent")
if parent is None:
# this doesn't work when MODIFYING existing elements!
if len(MyTest.objects.filter(name = name, parent = None)) > 0:
raise forms.ValidationError("name not unique")
return name
Details
The unique_together contraint works perfectly w/ the form when parent != None. However when parent == None (null) it allows duplicates to be created.
In order to try and avoid this, i tried using the form and defined clean_name to attempt to check for duplicates. This works when creating new objects, but doesn't work when modifying existing objects.
Someone had mentioned i should use commit=False on the ModelForm's .save, but I couldn't figure out how to do/implement this. I also thought about using the ModelForm's has_changed to detect changes to a model and allow them, but has_changed returns true on newly created objects with the form as well. help!
Also, (somewhat a completely different question) can I access the make_slug() model method from the Form? I believe that currently my exclude = ('slug',) line is also ignoring the 'unique' constraint on the slug field, and in the models save field, I'm generating the slug instead. I was wondering if i could do this in the forms.py instead?
You could have a different form whether you are creating or updating.
Use the instance kwarg when instantiating the form.
if slug:
instance = MyTest.object.get( slug=slug )
form = MyUpdateTestForm( instance=instance )
else:
form = MyTestForm()
For the second part, I think that's where you could bring in commit=False, something like:
if form.is_valid():
inst = form.save( commit=False )
inst.slug = inst.make_slug()
inst.save()
I don't know for sure this will fix your problem, but I suggest testing your code on the latest Django trunk code. Get it with:
svn co http://code.djangoproject.com/svn/django/trunk/
There have been several fixes to unique_together since the release of 1.02, for example see ticket 9493.
Unique together should be a tuple of tuples
unique_together = (("parent", "name"),)