Question

I have a hidden field on two forms which are rendered on the same page. This hidden field has a default setting of true. I use this hidden field to test which form has been submitted; the form that has been submitted will have that field's value set to true, and the other one won't have it set.

However, after submitting one form (the action of each form is the same page), the value of the hidden field of the other field will be set to blank. For example:

At first, the fields are rendered are like this (each in their own form, of course):

<input id="upload-submitted" name="upload-submitted" type="hidden" value="true">

...other stuff...

<input id="process-submitted" name="process-submitted" type="hidden" value="true">

If I submit the first form, it'll take me to the same page, and the fields are rendered like this:

<input id="upload-submitted" name="upload-submitted" type="hidden" value="true">

...other stuff...

<input id="process-submitted" name="process-submitted" type="hidden" value="">

If I submit the second form, then everything is the same except the first input has no value and the second one has the correct value.

The problem, specifically, seems to be that after submission, the data field of a submitted field is blank if that form wasn't submitted - that makes sense, that's what lets me determine which form was really submitted. However, the field does not reset for rendering in html - which means that I can't submit a different form after submitting the other.

How can I keep this from happening? Is there a better way to determine which of two forms were submitted on a page?

Here's my forms.py:

class HiddenSubmitted(object):
    """A mixin to provide a hidden field called "submitted" which has a default
    value of "true".
    """

    submitted = HiddenField(default="true")

class ButtonWidget(object):
    """A widget to conveniently display buttons.
    """
    def __call__(self, field, **kwargs):
        if field.name is not None:
            kwargs.setdefault('name', field.name)
        if field.value is not None:
            kwargs.setdefault('value', field.value)
        kwargs.setdefault('type', "submit")
        return HTMLString('<button %s>%s</button>' % (
            html_params(**kwargs),
            escape(field._value())
            ))

class ButtonField(Field):
    """A field to conveniently use buttons in flask forms.
    """
    widget = ButtonWidget()

    def __init__(self, text=None, name=None, value=None, **kwargs):
        super(ButtonField, self).__init__(**kwargs)
        self.text = text
        self.value = value
        if name is not None:
            self.name = name

    def _value(self):
        return str(self.text)

class MultiCheckboxField(SelectMultipleField):
    """
    A multiple-select, except displays a list of checkboxes.

    Iterating the field will produce subfields, allowing custom rendering of
    the enclosed checkbox fields.
    """
    widget = ListWidget(prefix_label=False)
    option_widget = CheckboxInput()

class DocumentUploadForm(Form, HiddenSubmitted):
    """This is a form to upload files to the server. It handles both XML
    and JSON files, and is used by the document_upload view.
    """

    upload_button = ButtonField(text="Upload", name="action")

    uploaded_file = FileField("File", validators=[
        FileRequired("You must select a file"),
        FileAllowed(app.config["ALLOWED_EXTENSIONS"])
        ])

class ProcessForm(Form, HiddenSubmitted):
    """
    Allows the user to select which objects should be
    processed/deleted/whatever.
    """

    PROCESS = "0"
    DELETE = "-1"

    files = MultiCheckboxField("Select", coerce=int, validators=[
        Required("You must select at least one item from the table.")
        ])
    process_button = ButtonField("Process", name="action", value=PROCESS)
    delete_button = ButtonField("Delete",  name="action", value=DELETE)

    def validate_files(form, field):
        if form.process_button.data == form.PROCESS:
            # There must be a JSON file selected
            struc_count = 0
            doc_count = 0
            for selected_file in form.files.data:
                unit = session.query(Unit).\
                    filter(Unit.id == selected_file).one()
                ext = os.path.splitext(unit.path)[1][1:]
                if ext in app.config["STRUCTURE_EXTENSION"]:
                    struc_count += 1
                else:
                    doc_count += 1
            if struc_count is not 1:
                raise ValidationError("Must be exactly one structure file")
            if doc_count < 1:
                raise ValidationError("At least one document must be selected")

And my views.py:

def really_submitted(form):
    """ WTForms can be really finnicky when it comes to checking if a form
    has actually been submitted, so this method runs validate_on_submit()
    on the given form and checks if its "submitted" field has any data. Useful
    for pages that have two forms on them.

    :arg Form form: A form to check for submission.
    :returns boolean: True if submitted, false otherwise.
    """

    if form.submitted.data == "true":
        return form.validate_on_submit()
    return False

@app.route(app.config["PROJECT_ROUTE"] + "<project_id>",
    methods=["GET", "POST"])
def project_show(project_id):
    """
    Show the files contained in a specific project. It also allows the user
    to upload a new document, much like projects().

    :param int project_id: The ID of the desired project.
    """

    upload_form = forms.DocumentUploadForm(prefix="upload")
    process_form = forms.ProcessForm(prefix="process")

    # If I put a print statement here, I find that by now one field has no data for the hidden field, which makes sense

    # The template needs access to the ID of each file and its filename.
    process_form.files.choices = []
    file_objects = #blah blah database stuff

    # First handle the actions of the upload form
    if shortcuts.really_submitted(upload_form):
        uploaded_files = request.files.getlist("upload-uploaded_file")
        for uploaded_file in uploaded_files:
            filename = secure_filename(uploaded_file.filename)
            dest_path = os.path.join(app.config["UPLOAD_DIR"],
                str(project_id), filename)
            uploaded_file.save(dest_path)
            unit = Unit(path=dest_path, project=project_id)
            unit.save()
            process_form.files.choices.append((unit.id,
                os.path.split(dest_path)[1]))

    # Or what happened with the document selection
    elif shortcuts.really_submitted(process_form):
        files = request.form.getlist("process-files")
        if request.form["action"] == process_form.DELETE:
            # Delete every selected file, its database record, and item in
            # the listing
            for file_id in files:
                file_model = session.query(Unit).\
                    filter(Unit.id == file_id).one()
                file_name = os.path.split(file_model.path)[1]
                os.remove(file_model.path)
                session.delete(file_model)
                session.commit()
                process_form.files.choices.remove((int(file_id), file_name))

        elif request.form["action"] == process_form.PROCESS:
            #TODO: process these files.
            pass

    return render_template("document_list.html",
        upload_form=upload_form,
        process_form=process_form)
Was it helpful?

Solution

At least, one solution is this, right before rendering the template:

process_button.submitted.data = "true"
delete_button.submitted.data = "true"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top