Separating Validation Logic in Django Forms

I’m attempting to centralize all form validation in a separate validators.py file and avoid performing any validation in forms.py or views.py. Is this considered a standard practice? I want to separate the validation for simplicity, but it seems like Django is making it difficult for me. I find it challenging to access the necessary data for validation, and the process feels more complex compared to typical Django conventions.

I intentionally want to avoid using clean() methods in forms.py because that would involve including validation in the forms module rather than the validators module. However, I’m unsure if I’m doing something wrong.

For instance, to access request.user for authentication validation in my specific case, I had to resort to an unwieldy solution I found on Stack Overflow to make it work:

def __init__(self, *args, **kwargs):
    current_user = kwargs.pop('current_user', None)
    super(UploadFileForm, self).__init__(*args, **kwargs)
    self.fields["is_public"].validators.append(validate_user_authentication(current_user))

def validate_user_authentication(current_user):
    def validate(value):
        if value and not current_user.is_authenticated:
            raise ValidationError("This field is only accessible to authenticated users.")
    return validate

Although I’m still unsure about the inner workings of this approach, it at least functions as intended.

Now, I’m facing a challenge with validating the number of files uploaded in my multi-upload feature. I’m unable to access the file count from request.FILES.getlist("uploaded_files") within the views module.

uploaded_files = MultipleFileField(
    required=True,
    allow_empty_file=False,
    validators=[
        validate_file_size,
        validate_number_of_files],
)

def validate_number_of_files(value):
    max_allowed_files = settings.MAX_FILE_COUNT
    if len(value) > max_allowed_files:
        raise ValidationError(f"Please upload a maximum of {max_allowed_files} files.")

The above approach doesn’t work because the literal file is being passed as the argument. How should I handle this situation? I would prefer not to continue modifying the __init__ method, which I barely understand.

It’s definitely not.

It’s been my experience that the form-level validation is very specific to the form being validated. Rarely do I see two forms where all the validation is the same between them.

Having said that…

It’s not that you’re doing something “wrong”, it’s just that you’re not quite following this train of thought through to a conclusion.

Nothing prevents you from creating a library of “validation functions” - and then calling those functions from your form-level validation. In other words, if you have a clean_user function that needs to be called from multiple forms allowing for the entry of a user field, you could do something like:

from my_validators import my_clean_user

class MyForm(Form):
   ...
   def clean_user(self):
     return my_clean_user(self)

That’s pretty much how it’s done. (Well, I wouldn’t have done it quite that way. I would have attached current_user to self, allowing me to use validate_user directly without having to attach an additional function.)

(Why don’t you have access to request in a form? Because there’s no requirement for there to be a request object to be associated with a form. You can use forms to validate data being passed around your system under a variety of circumstances that don’t directly involve a request from a browser.)

See the code example in the section Uploading multiple files.

My understanding from this (a bit of a guess here - I’ve not tried this) is that you could override clean_upload_field. Call super to get what the example refers to as files and interrogate it to see how many were supplied.