angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Adición de un formulario de contacto a una página multilingüe con contenido de una base de datos

Cuando el contenido de la página proviene de una base de datos, querrá añadir un formulario de contacto utilizando una etiqueta.

28 septiembre 2019 Actualizado 15 octubre 2019
post main image
unsplash.com/@nickmorrison

Actualización 11 de octubre de 2019: Cambié la etiqueta addon de '{% addon: ... %}' a '[[ addon: ... ]]". La razón es que quería poder renderizar el texto de la página procedente de la base de datos, usando render_template_string, y '{% ... %}' conflictos con las Jinja2 etiquetas. Y sí, no quiero implementar una etiqueta Jinja2 personalizada.

¿Qué tiene de difícil implementar una página de contacto con un formulario de contacto con Flask y WTForms? Puede encontrar soluciones sobre cómo implementar una página de contacto en Flask pero cada vez que la página es una página en un solo idioma y utiliza un archivo de Jinja2 plantilla. Entonces, ¿por qué escribir un post sobre esto?

La razón es que esto no es trivial cuando el contenido de la página puede ser multilingüe y proviene de una base de datos. Tengo el contenido de la página que puedo editar usando el administrador, y quiero que el formulario de contacto se coloque en algún lugar de la página usando una etiqueta. ¿Por qué una etiqueta? Porque debemos ser capaces de poner el formulario de contacto en cualquier lugar del contenido. Una vez que la etiqueta es reemplazada por el formulario de contacto, también debe ser procesada cuando se envía. ¿Fácil? Quizá para ti, pero no para mí.

Presentación de los complementos

En cuanto a otras soluciones, pensé que sería útil implementar el formulario de contacto como un complemento. Por qué? Porque un complemento es algo que deberías poder añadir de una manera muy sencilla a tu contenido. También debería ser posible añadir el formulario de contacto a varias páginas. Hay más que un complemento, por ejemplo, el complemento de formulario de contacto también añade una función de formulario de contacto al administrador, donde podemos ver los formularios de contacto que se han enviado.

Implementación del add-on

Lo primero que hice fue definir una etiqueta que identificara el complemento del formulario de contacto:

{% addon:contact_form,id=87 %}

Esta es la etiqueta que podemos añadir al contenido de nuestra página multilingüe que proviene de la base de datos. Otros componentes del complemento del formulario de contacto son:

  • ContactForm, el modelo (tabla)
  • Parte administrativa, donde podemos ver los formularios enviados

Y luego necesitamos un mecanismo general que procese el complemento cuando mostramos una página. Como recordará de un post anterior, sólo hay una función que genera una página. Como el contenido no cambia, se almacena en caché:

@pages_blueprint.route('/<slug>', methods=['GET', 'POST'])
def page_view(slug):
    ...
    
    # get content_item

    ...

    # render content_item of get from cache
    hit, rendered_content_item = current_app.app_cache.load(cache_item_id)
    if not hit:
        rendered_content_item = render_template(
             ...
            content_item=content_item,
            content_item_translation=content_item_translation,
            )
        # cache it
        current_app.app_cache.dump(cache_item_id, rendered_content_item)

     ...

    return render_template(
        ...
        rendered_content_item=rendered_content_item,
        )

Esta función debe modificarse y ampliarse para que pueda manejar complementos.

Convertir MVC en una clase

En el Flask uso WTForms del formulario de contacto, la implementación es muy sencilla, por ejemplo:

@pages_blueprint.route('/contact-form', methods=['GET', 'POST'])
def contact_form():

    form = ContactFormForm()

    if form.validate_on_submit():
        contact_form = ContactForm()
        form.populate_obj(contact_form)
        db.add(contact_form)
        db.commit()
        flash( _('Contact form submitted.'), 'info')
        return redirect(url_for('pages.thank_you'))

    return render_template(
        'pages/contact_form.html', 
        form=form)

Y el formulario de contacto es:

class ContactFormForm(FlaskForm):

    name = StringField(_l('Name'), validators=[
        Length(min=2, max=60),
        InputRequired()])

    email = StringField(_l('Your email'), validators=[
        InputRequired(), 
        Email()])

    message = TextAreaField(_l('Your message'), validators=[
        Length(min=6, max=500),
        InputRequired()])

    submit = SubmitField(_l('Send'))

No podemos usar esto aquí, así que lo reescribimos como una clase. Decidí devolver el éxito o error de los métodos GET y POST y tener un método separado para obtener el formulario de contacto renderizado.

class AddonContactForm:
 
    def __init(self)__:
        ...
        self.errors = False
        self.rendered_contact_form = ''

    def get_contact_form(self):

        self.errors = False

        form = ContactFormForm()

        self.rendered_contact_form = render_template(
            'addons/contact_form.html', 
            form=form)

        return self.errors

    def get_rendered_contact_form(self):
        return self.rendered_contact_form
        
    def post_contact_form(self):

        self.errors = False

        form = ContactFormForm()

        if form.validate_on_submit():
            contact_form = ContactForm()
            form.populate_obj(contact_form)
            db.add(contact_form)
            db.commit()
            flash( _('Contact form submitted.'), 'info')
            return redirect(url_for('pages.thank_you'))

        self.errors = True

        self.rendered_contact_form = render_template(
            'addons/contact_form.html', 
            form=form)

        return self.errors

El ContactFormFormForm se amplía con un parámetro oculto que identifica al complemento:

    addon_id = HiddenField('Addon id')

Usando esto ahora podemos cambiar la función page_view:

@pages_blueprint.route('/<slug>', methods=['GET', 'POST'])
def page_view(slug):
    ...

    if request.method == 'POST':
        addon_id = None
        if 'addon_id' in request.form:
            addon_id = request.form['addon_id']
        if addon_id is not None:
            if addon_id == 'contact_form':
                addon_contact_form = AddonContactForm()
                if addon_contact_form.process_contact_form():
                    addon_redirect_url = addon_contact_form.get_redirect_url()
                    return redirect(addon_redirect_url)

                # error(s) found during processing
                rendered_contact_form = addon_contact_form.get_rendered_contact_form()
                addon_error = True
                addon_error_message = addon_contact_form.get_error_message()

    ....
    
    # addon: processing if '{% addon' found 
    if '{% addon:' in rendered_content_item:

        m = re.findall('\{\%\s*(addon)\s*\:\s*([a-z_]+)\s*.*?\%\}', rendered_content_item)
        addon_name = None
        if m:
            addon_name = m[0][1]

        if addon_name == 'contact_form':
            if request.method == 'GET':

                addon_contact_form = AddonContactForm()
                if addon_contact_form.get_contact_form():
                    rendered_contact_form = addon_contact_form.get_rendered_contact_form()
                else:
                    rendered_contact_form = ''
                    error = True
                    error_message = addon_contact_form.get_error_message()
                    
                rendered_content_item = re.sub('\{\%\s*(addon)\s*\:\s*([a-z_]+)\s*.*?\%\}', rendered_contact_form, rendered_content_item)

            elif request.method == 'POST':
                # here we just paste the result from the addon
                # typically we only come here when an error was detected in the form

                rendered_content_item = re.sub('\{\%\s*(addon)\s*\:\s*([a-z_]+)\s*.*?\%\}', rendered_contact_form, rendered_content_item)


    return render_template(
    ...
    )

Resumen

Lo anterior es meramente un resumen, hay más, pero sólo quería darles lo básico. También implementé un complemento de preguntas frecuentes, donde sólo tenemos que tratar con un GET. Puede consultar las páginas de Contacto y Preguntas Frecuentes en este sitio web. Esto fue sólo un primer intento de implementar complementos, y no, no es definitivo. Ahora debería definir una interfaz clara de todos los métodos y atributos que un add-on puede o debe utilizar. En otro momento...

Enlaces / créditos

Exact difference between add-ons, plugins and extensions
https://stackoverflow.com/questions/33462500/exact-difference-between-add-ons-plugins-and-extensions

Intro to Flask: Adding a Contact Page
https://code.tutsplus.com/tutorials/intro-to-flask-adding-a-contact-page--net-28982

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.