Django Single Field Validation cleaned_data - django

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.

Related

Django rest framework : extend user model for customer - one to one field

I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
When I call the below code through API, it shows the following error. But data is saving in the tables. I also want to implement the get and put calls for this api.
Got AttributeError when attempting to get a value for field `city` on serializer `CustomerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'city'.
my Bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
my Bcustomer/serializers.py
from django.contrib.auth import get_user_model
from models import BCustomer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
city = serializers.CharField()
class Meta:
model = get_user_model()
fields = ('first_name', 'email','city')
def create(self, validated_data):
userModel = get_user_model()
email = validated_data.pop('email', None)
first_name = validated_data.pop('first_name', None)
city = validated_data.pop('city', None)
request = self.context.get('request')
creator = request.user
user = userModel.objects.create(
first_name=first_name,
email=email,
# etc ...
)
customer = BCustomer.objects.create(
customer=user,
city=city,
user=creator
# etc ...
)
return user
my Bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
my Bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
POST request format
{
"first_name":"Jsanefvf dss",
"city":"My City",
"email":"myemail#gmail.com",
#more fields
}
I also need to implement put and get for this api. Now data is saving in both tables but shows the error.
Sure it complains.
Validation goes well, so does the creation but once it's created, the view will deserialize the result and return it to the client.
This is the point where it goes south. Serializer is configured to think that city is a field of the default user while it actually is part of BCustomer. In order to work this around, you should set the source argument to the city field. You might need to update the serializer's create/update to reflect that change, not sure about that one.

Django form doesn't get the value in clean method

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

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

While saving a Django model instance, in what order are my clean() and save() overrides applied relative to methods used as ModelField attributes?

I have a model with a first_name and last_name field, and these are used to create a filename on an ImageField. The argument for upload_to on the ImageField is this method that generates the filename with this instance's information.
When this model instance is saved, would the calls to .strip() in the clean() be applied to the fields before they are used to generate the filename? Or would I need to do .strip() on the data when it's used, as well as in the clean?
models.py:
def set_path(instance, filename):
"""
Set the path to be used for images uploaded (trainer photos).
"""
return u'about/%(first)s_%(last)s.%(ext)s' % {
'first': instance.first_name.strip(' \t').lower(), #.strip() required?
'last': instance.last_name.strip(' \t').lower(), #.strip() required?
'ext': filename.split('.')[-1]
}
class Trainer(models.Model):
"""
Trainers and their associated information.
"""
first_name = models.CharField(max_length=25)
last_name = models.CharField(max_length=25)
image = models.ImageField(upload_to=set_path, blank=True, null=True,
verbose_name="Trainer image")
description = models.TextField()
class Meta:
unique_together = ('first_name', 'last_name',)
def clean(self):
super(Trainer, self).clean()
# Are these calls to .strip() applied before the fields
# get used as `instance` to determine a filename?
self.first_name = self.first_name.strip(' \t')
self.last_name = self.last_name.strip(' \t')
self.description = self.description.strip(' \t\r\n')
If there is a callable for the upload_to argument, it is called during the save() method of the model base. The save() is of course called after clean(), so you do NOT need to strip() any fields if you've already done so in the clean() method.
You can see where the code is called on line 90 of the Django source code: https://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py
generate_filename is the stored variable that points to whatever you passed into upload_to.
So, the order is form submit -> model.full_clean() -> overridden clean() -> save(), which calls upload_to()

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"),)