Python Flask WTForms: Dynamic SelectField, возвращающий «Недопустимый выбор» - PullRequest
0 голосов
/ 01 ноября 2019

Во-первых, я знаю, что здесь есть десятки подобных вопросов. Я просмотрел большинство, если не все из них, и ни один из них не помог мне найти решение. Следующий вопрос является наиболее похожим, но моя реализация «dynamic» немного отличается от их (подробнее об этом ниже): Проверка Flask / Python / WTForms и динамическая установка выбора SelectField

Вкратце:

У меня есть форма, которая используется для запроса отчета из созданного мной инструмента мониторинга сети. Инструмент отслеживает все виды статистики для различных беспроводных сетей. Вот определение класса формы. Моим динамическим полем является ssidFilter selectField.

class RequestReportForm(FlaskForm):
    startDate = DateField('Start Date', validators=[DataRequired(), validate_startDate])
    startTime = TimeField('Start Time', format='%H:%M', validators=[DataRequired()])
    endDate = DateField('End Date', format='%Y-%m-%d', validators=[DataRequired(), validate_endDate])
    endTime = TimeField('End Time', format='%H:%M', validators=[DataRequired(), validate_allDates])
    ssidFilter = SelectField('SSID', default=('All', 'All'))
    reportType = SelectField('Report Type', validators = [DataRequired()], choices=[
                                                                ('rssi', 'RSSI vs. Time'), 
                                                                ('snr', 'SNR vs. Time'),
                                                                ('ClientCount', 'Client Count vs. Time'),

    ])
    selectLocation = SelectField('Locations', validators = [DataRequired()], choices=[ 
                                                                ('All','All'),
                                                                ('mainLobby', 'Main Lobby'), 
                                                                ('level1', 'Level 1'), 
                                                                ('level2', 'Level 2'),
                                                                ])
    submit = SubmitField('Generate Report')

Я уже реализовал Javascript, чтобы взять введенные пользователем поля startDate и endDate и выполнить запрос к моей базе данных, "выбрав" другой маршрут фляги в моемприложение, чтобы вернуть список всех беспроводных сетей (SSID), которые были использованы в диапазоне дат, которые они ввели. Вот этот маршрут:

@app.route('/updateSSIDs/<startDate>/<endDate>', methods=['GET'])
def updateSSIDs(startDate, endDate):
    startDate = datetime.strptime(startDate, '%Y-%m-%d')
    endDate = datetime.strptime(endDate, '%Y-%m-%d')
    # Get a list of unique SSIDs that we have data for between the start and end dates selected on the form.
    SSIDs = getSSIDs(startDate, endDate)
    SSIDArray = []
    for ssid_tuple in SSIDs:
        ssidObj = {}
        ssidObj['id'] = ssid_tuple[0]
        ssidObj['ssid'] = ssid_tuple[0]
        SSIDArray.append(ssidObj)
    return jsonify({'SSIDs' : SSIDArray})

Переменная SSIDArray выглядит так, как она до jsonify'd:

[{'id': 'Example Network 1', 'ssid': 'Example Network 1'}, {'id': 'Staff', 'ssid': 'Staff'}, ... ]

Вот как я создаю экземпляр формы:

@app.route('/requestReport', methods=['GET', 'POST'])
def requestReport():
    form = RequestReportForm()
    form.ssidFilter.choices = getSSIDs(datetime.now(), datetime.now())

    if form.validate_on_submit():
        print("Valid form data:")
        print(form.data)
        flash(f'Received request for report from {form.startDate.data} at {form.startTime.data} through {form.endDate.data} at {form.endTime.data}', 'success')
        startDate = form.startDate.data
        startTime = form.startTime.data
        endDate = form.endDate.data
        endTime = form.endTime.data

        reportType = form.reportType.data
        locations = form.selectLocation.data
        ssid = form.ssidFilter.data

        # Put requested times into datetime objects
        startDateTime = datetime(startDate.year, startDate.month, startDate.day, startTime.hour, startTime.minute)
        endDateTime = datetime(endDate.year, endDate.month, endDate.day, endTime.hour, endTime.minute)

        # Generate report and redirect client to report.
        reportParameters = rpt.prepareReport_single(startDateTime, endDateTime, reportType, locations, ssid)        
        report = rpt.buildReport_singleLocation(reportParameters)       
        report = Markup(report)
        return render_template('viewReport.html', value=report)

Обратите внимание, что я заполняю свое динамическое поле, здесь form.ssidFilter.choices, вызывая ту же самую функцию getSSIDs, которая отвечает на мой вызов выборки Javascript, но я передаю datetime.now() как для начала, так и дляДата окончания. Это должно сначала показать пользователю список беспроводных сетей, которые в настоящее время используются, но как только они изменят даты, список обновится с другим набором сетей.

И в этом заключается проблема:Как я могу настроить список допустимых вариантов (form.ssidFilter.choices), чтобы он содержал список сетей, который возвращается после того, как клиент вводит даты для отчета?

Возможные решения, которые я изучаю:

  • Перезагрузка страницы после выбора даты для создания новой формы с динамическими данными.

  • Сначала сохраните огромный список всех доступных вариантов, а затемвыбор будет динамически фильтроваться через JS, когда пользователь изменяет даты в форме.

О, и форма отлично работает, если выбранный SSID является SSID, который был в списке из оператора form.ssidFilter.choices = getSSIDs(datetime.now(), datetime.now()). Эта проблема возникает только тогда, когда выбран элемент, которого изначально не было в списке вариантов (что имеет смысл - я просто не знаю, как его решить).

Спасибо за потраченное время.

РЕДАКТИРОВАТЬ / Решение:

Благодаря ответу @ SuperShoot я смог заставить это работать. Для меня ключевым было сделать так, чтобы маршрут Flask различал тип HTTP-запроса - GET или POST. Поскольку я знал, что метод GET используется только для извлечения формы, а метод POST - только для отправки заполненной формы, я мог извлечь выборки startDate и endDate от пользователя, выполнить запрос для получения данных и обновитьполе choices из моего класса формы.

Мне пришлось провести дополнительную проверку, как и в @SuperShoot, но я сделал это немного по-другому. Поскольку мой код JavaScript вызывает отдельный маршрут из моего приложения Flask, как только изменяется дата окончания, форма не несет ответственности за проверку выбранной даты. Я реализовал некоторую проверку в этом другом маршруте Flask.

Вот мой измененный маршрут Flask requestReport:

@app.route('/requestReport', methods=['GET', 'POST'])
def requestReport():
    form = RequestReportForm()
    form.ssidFilter.choices = getSSIDs(datetime.now(), datetime.now())

    if request.method == 'POST':
        startDate = datetime(form.startDate.data.year, form.startDate.data.month, form.startDate.data.day)
        endDate = datetime(form.endDate.data.year, form.endDate.data.month, form.endDate.data.day)
        # Update acceptable choices for the SSIDs on the form if the form is submitted.
        form.ssidFilter.choices = getSSIDs(startDate, endDate)

    if form.validate_on_submit():
        flash(f'Received request for report from {form.startDate.data} at {form.startTime.data} through {form.endDate.data} at {form.endTime.data}', 'success')
        startDate = form.startDate.data
        startTime = form.startTime.data
        endDate = form.endDate.data
        endTime = form.endTime.data

        reportType = form.reportType.data
        locations = form.selectLocation.data
        ssid = form.ssidFilter.data

        # Put requested times into datetime objects
        startDateTime = datetime(startDate.year, startDate.month, startDate.day, startTime.hour, startTime.minute)
        endDateTime = datetime(endDate.year, endDate.month, endDate.day, endTime.hour, endTime.minute)

        # Generate report and redirect client to report.
        reportParameters = rpt.prepareReport_single(startDateTime, endDateTime, reportType, locations, ssid)        
        report = rpt.buildReport_singleLocation(reportParameters)       
        report = Markup(report)
        return render_template('viewReport.html', value=report)

    else:
        return render_template('requestReport.html', title='Report Request', form=form)

А вот мой обновленный маршрут updateSSIDs, который вызывается через Javascript, когдадата окончания формы изменяется:

@app.route('/updateSSIDs/<startDate>/<endDate>', methods=['GET'])
def updateSSIDs(startDate, endDate):

    startDate = datetime.strptime(startDate, '%Y-%m-%d')
    endDate = datetime.strptime(endDate, '%Y-%m-%d')

    # Validate startDate and endDate
    emptyDataSet = {'SSIDs' : {'id ': 'All', 'ssid' : 'All'}}
    if startDate > endDate:
        return jsonify(emptyDataSet)

    if startDate >= datetime.now():
        return jsonify(emptyDataSet)

    if startDate.year not in range(2019, 2029) or endDate.year not in range(2019, 2029):
        return jsonify(emptyDataSet)

    # Get a list of unique SSIDs that we have data for between the start and end dates selected on the form.
    SSIDs = getSSIDs(startDate, endDate)
    SSIDArray = []
    for ssid_tuple in SSIDs:
        ssidObj = {}
        ssidObj['id'] = ssid_tuple[0]
        ssidObj['ssid'] = ssid_tuple[0]

        SSIDArray.append(ssidObj)
    return jsonify({'SSIDs' : SSIDArray})

Этот маршрут выполняет некоторые базовые проверки, чтобы убедиться, что отправленные даты не являются полностью нелепыми, прежде чем пытаться получить данные из базы данных с помощью getSSIDs, но я делаюнемного более тщательной проверки в функции getSSIDs.

1 Ответ

1 голос
/ 01 ноября 2019

Вы можете создать экземпляр формы по-разному в зависимости от того, обрабатывает ли маршрут запрос GET или POST:

@app.route('/requestReport', methods=['GET', 'POST'])
def requestReport():
    form = RequestReportForm()
    if request.method == "GET":
        start = end = datetime.now()
    else:
        # validate start and end dates here first?
        start, end = form.startDate.data, form.endDate.data
    form.ssidFilter.choices = getSSIDs(start, end)
    ...

... хотя в случае POST используются даты начала и окончания до того, как они былиПРОВЕРЯЕМЫЕ. Таким образом, один из вариантов - сначала проверить их в строке внутри обработки условия «POST» (где я разместил комментарий), или другой - переопределить метод .validate() для RequestReportForm.

. Этострока документа Form.validate():

        """
        Validates the form by calling `validate` on each field.
        :param extra_validators:
            If provided, is a dict mapping field names to a sequence of
            callables which will be passed as extra validators to the field's
            `validate` method.
        Returns `True` if no errors occur.
        """

Возможная реализация:

class RequestReportForm(FlaskForm):
    ...

    def validate(self, *args, **kwargs):
        """Ensure ssidFilter field choices match input startDate and endDate"""
        if not (self.startDate.validate(self) and self.endDate.validate(self)):
            return False
        self.ssidFilter.choices = getSSIDs(self.startDate.data, self.endDate.data)
        return super().validate(*args, **kwargs)

FlaskForm.validate_on_submit() сначала проверяет, что форма отправлена, а затем будетвызовите пользовательский метод .validate(). Сначала этот метод проверяет правильность дат начала и окончания и использует их для заполнения ожидаемых возможных значений для ssidFilter, прежде чем окончательно делегировать проверку резервной копии MRO.

Я не запускал этот код, поэтому позвольте мнезнаю, есть ли ошибки, но, надеюсь, у меня есть идея достаточно хорошо, чтобы вы могли работать с ней, если она подходит.

...