kubetail-org/starlette-wtf

Add FileRequired validator?

Opened this issue · 2 comments

The DataRequired does not properly work for FileField, because it returns a starlette.datastructures.UploadFile and is thus not empty (even if no file is uploaded). So it might be worthwhile to add a FileRequired validator. You can repro this with:

from jinja2 import Template
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import PlainTextResponse, HTMLResponse
from starlette_wtf import StarletteForm, CSRFProtectMiddleware, csrf_protect
from wtforms import FileField
from wtforms.validators	import DataRequired


class MyForm(StarletteForm):
    photo = FileField(validators=[DataRequired()])


template = Template('''
<html>
  <body>
    <form method="post" novalidate enctype="multipart/form-data">
      {{ form.csrf_token }}
      <div>
        {{ form.photo() }}
        {% if form.photo.errors -%}
        <span>{{ form.photo.errors[0] }}</span>
        {%- endif %}
      </div>
      <button type="submit">Submit</button>
    </form>
  </body>
</html>
''')


app = Starlette(middleware=[
    Middleware(SessionMiddleware, secret_key='***REPLACEME1***'),
    Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***')
])


@app.route('/', methods=['GET', 'POST'])
@csrf_protect
async def index(request):
    """GET|POST /: form handler
    """
    form = await MyForm.from_formdata(request)

    if await form.validate_on_submit():


        print(form.photo.data)
        # print(form.photo.size)

        return PlainTextResponse(f'Data Size: {form.photo.data.size}')


    html = template.render(form=form)
    return HTMLResponse(html)

The validator could look like this (modeled after the DataRequired validator):

from wtforms.validators import StopValidation

class FileRequired:
    def __init__(self, message=None):
        self.message = message
        self.field_flags = {"required": True}

    def __call__(self, form, field):
        if field.data and field.data.size > 0:
            return
        if self.message is None:
            message = field.gettext("This field is required.")
        else:
            message = self.message

        field.errors[:] = []
        raise StopValidation(message)

class MyForm(StarletteForm):
    photo = FileField(validators=[FileRequired()])

Thanks, this looks very useful! Would you be interested in submitting a PR to add file validators to starlette-wtf like flask-wtf's: https://github.com/wtforms/flask-wtf/blob/main/src/flask_wtf/file.py?