【问题标题】:How to validate CSV file upload on the fly in Django using a custom class-based validator?如何使用基于类的自定义验证器在 Django 中即时验证 CSV 文件上传?
【发布时间】:2020-11-06 03:31:24
【问题描述】:

我有一个用于将 CSV 文件内容注入数据库的上传表单。到目前为止,我正在努力验证本身。我从基于函数的验证器切换到基于类的验证器,因此我可以将参数传递给验证器本身以使其可重用。

views.py

@login_required
def contact_import(request):
    if request.method == 'POST' and request.FILES['file']: 
        form = ContactImportForm(request.POST,request.FILES)

        if form.is_valid():
            post_result = form.save(commit=True)

            return redirect("/contacts/")
    else:
        form = ContactImportForm()
        
    return render(request,'contact_import_form.html',{
        'form': form
    })  

forms.py

class ContactImportForm(forms.Form):
    headers = ["lastname","firstname","email"]
    file = forms.FileField(label='Select your CSV File',validators=[CsvFileValidator(headers)])
    
    def clean_file(self):
        file = self.cleaned_data['file']
        return file    

validators.py

class CsvFileValidator(object):
    def __init__(self, headers):
        self.headers = headers
    
    def __call__(self,file):

        # Extension check
        file_extension = os.path.splitext(file.name)[1]
        valid_extensions = [ ".csv", ".CSV"]
        
        if not file_extension.lower() in valid_extensions:
            msg = "Invalid file, select a valid CSV file"
            raise ValidationError("{}".format(msg), status='invalid')

        # File content check
        try:
            csv_format = csv.Sniffer().sniff(file.read(1024))
            file.seek(0,0)    
        except csv.Error:
            msg = "Invalid file, select a valid CSV file"
            raise ValidationError("{}".format(msg), status='invalid')
        
        csv_reader = csv.reader(file.read().splitlines(), dialect=csv_format,delimiter=";")

        for r_index, row in enumerate(csv_reader):

            # CSV headers check
            if r_index == 0:
                if sorted(self.headers) != sorted(row):
                    msg = "Missing or invalid headers"
                    raise ValidationError("{}".format(msg), status='invalid')   
     
            # Skip blank line
            if not "".join(str(field) for field in row):
                continue
        
        return True    

contact_import_form.html HTML 模板,仅相关部分:

<div class="messages">
{% if form.errors %}
    {% if form.non_field_errors %}
    <div class="alert alert-danger" role="alert">
        <p class="text-danger"><strong>AN error occured during form validation</strong>
        {% for error in form.non_field_errors %}
            {{error}}
        {% endfor %}  
        </p>
   
    {% endif %}
    
    {% for field in form %}
        {% if field.errors %}
            <p class="text-danger"><strong>{{ field.name }} :</strong> {{ field.errors }}</p>
        {% endif %}
    {% endfor %}
     </div>
{% endif %}
</div>

<form id="contact-import-form" method="post" action=" " role="form" enctype="multipart/form-data">
{% csrf_token %}
    <div class="form-group">
        {{ form.file.label_tag }}
        {{ form.file }}
    </div>

    <div class="form-group">
      <label class="col-md-4 control-label" for="submit"></label>
      <div class="col-md-8">
        <button id="submit" name="submit" class="btn btn-primary" value="Submit">Valider</button>
        <a href="${%url 'contact_list' %}" id="cancel" name="cancel" class="btn btn-default">Annuler</a>
      </div>
    </div>    
</form>

现在提交表单后出现错误:

TypeError: cannot use a string pattern on a bytes-like object

这个错误指向我的验证器中的文件内容检查,特别是上面的行:

csv_format = csv.Sniffer().sniff(file.read(1024))

如何实现这个自定义的基于类的验证器才能正常工作?另外我想更新数据库的缺失代码应该存在于if form.is_valid():代码块的视图中。

如何使用基于类的验证器即时验证 CSV 文件上传,然后使用其数据填充模型?

【问题讨论】:

  • 当传递给 CsvFileValidator 时,'file' 类型为 "'django.core.files.uploadedfile.InMemoryUploadedFile"。在 io.TextIOWrapper 中包装文件似乎没问题。但是,验证器引发的异常仍然没有出现在我的模板中。
  • 表单验证按预期工作,但在 form.errors 或 form.non_field_errors 中没有出现异常消息。如何在其中提供验证异常消息?
  • 看来是验证器本身的问题,但我不知道如何正确解决问题。如果没有验证器,如果表单有效,则可以读取文件。

标签: django csv django-forms


【解决方案1】:

问题在于文件的类型,file.read(1024) 不返回字符串,要解决此问题,您应该将输出解码为 csv_format = csv.Sniffer().sniff(file.read(1024).decode('UTF-8'))

【讨论】:

    【解决方案2】:

    当调用Form的clean方法,想要查看文件内容时,需要知道如何正确打开InMemoryUploadedFile类型。 This 答案显示如何打开上传的文件。

    正如 donmelchior 在上面的评论中指出的那样,您的 CsvFileValidator 类似乎打开文件不正确。尝试类似

    import csv
    import io
    
    decoded_file = file.read().decode("utf-8")
    io_string = io.StringIO(decoded_file)
    csv_reader = csv.DictReader(io_string)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-23
      • 2023-04-04
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 2012-07-29
      • 1970-01-01
      相关资源
      最近更新 更多