﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
31833	"FileField with a custom validator leads to ""I/O operation on closed file""."	elmajax	Harpreet Sharma	"After posting a form with a unique FileField, ""I/O operation on closed file"" exception occurs. Code provoking the error is within the view. I doubled checked that form is properly validated from view and validator.

Strangely the validator class (file is processed differently) doesn't throw an  ""I/O operation on closed file"" exception when file is converted with io.TextIOWrapper and processed (see validators.py code below) and work properly.

It seems that somehow file (in memory) is flushed right after being posted without any mean to process it further with form.is_valid().

Tested on Django 2.2.25 and 3.0.8.

**forms.py**

{{{

from .validators import CsvFileValidator

class FileImportForm(forms.Form):
    headers = [""lastname"",""firstname"",""gender"",""title"",""entity"",""email"",""company"",""address"",""phone""]
    file = forms.FileField(label='CSV file',validators=[CsvFileValidator(headers)])
    
    def clean_file(self):
        file = self.cleaned_data['file']
        return file  
}}}

**views.py**

{{{
@login_required
def file_import(request):
    if request.method == 'POST': 
        form = FileImportForm(request.POST,request.FILES)

        if form.is_valid():
            if request.FILES['file']:
                file_post = request.FILES['file']
                # Offending line below (I/O operation on closed file)
                file_content = file_post.read().decode('UTF-8')

                return redirect(""/foo"")
    else:
        form = FileImportForm()
    
    return render(request,""file_import.html"", { 'form': form })
}}}

**validators.py**

{{{
import csv 

class CsvFileValidator(object):
    
    def __init__(self, headers):
        self.headers = headers
    
    def __call__(self,file):

        file_extension = os.path.splitext(file.name)[1]        
        valid_extensions = [ "".csv"", "".CSV""]
        
        if not file_extension.lower() in valid_extensions:
            msg = ""Invalid file extension""
            logger.error(msg)
            raise ValidationError(""{}"".format(msg), code='invalid')

        try:
            csv_file = io.TextIOWrapper(file) 
            csv_format = csv.Sniffer().sniff(csv_file.read(1024))
            csv_file.seek(0,0)      
        except csv.Error:
            msg = ""Invalid CSV file""
            logger.error(msg)
            logger.debug(""Exception msg : {}"".format(msg))
            
            raise ValidationError(""{}"".format(msg), code='invalid')

        csv_reader = csv.reader(csv_file.read().splitlines(), dialect=csv_format,delimiter="";"")

        for r_index, row in enumerate(csv_reader):

            if r_index == 0:
                if sorted(self.headers) != sorted(row):
                    msg = ""Invalid or missing CSV headers""
                    logger.error(msg)
                    
                    raise ValidationError(""{}"".format(msg), code='invalid')            

            if not """".join(str(field) for field in row):
                continue
        
        msg = ""Valid CSV file""
        logger.debug(msg)
        
        return True     

    def __eq__(self,other):        
        return (
            isinstance(other, self.__class__)
            and self.headers == other.headers
        )
}}}

 "	Bug	closed	Forms	3.0	Normal	invalid			Unreviewed	0	0	0	0	0	0
