Compartiendo modelos, clases, Blueprints y plantillas entre aplicaciones con Flask DispatcherMiddleWare
Al evitar la duplicación, su código se hace más pequeño y más fácil de mantener.
Este sitio está corriendo Flask. Utiliza DispatcherMiddleWare para ejecutar la aplicación de frontend y la aplicación de administración. Los documentos Flask establecen que las aplicaciones Flask en este caso están completamente aisladas unas de otras. Eso es cierto, pero a menudo hay mucho código que queremos compartir entre estas aplicaciones.
Cosas que queremos compartir
Ambas aplicaciones usan la misma base de datos, lo que significa que queremos compartir el archivo models.py. Entonces tenemos ciertas clases que escribimos nosotros mismos. Por ejemplo, yo escribí clases como MailMessage y FormValidation. Deberían ser usadas por ambas aplicaciones.
También estoy usando Blueprints que debe ser compartido, por ejemplo el 'auth' Blueprint que maneja las funciones de autenticación como iniciar sesión, crear cuenta, restablecer contraseña. Las plantillas usadas por estos Blueprints también deben ser compartidas. También hay otras plantillas que deberían ser compartidas, como macros para poner formularios en una página, macros que ponen botones en tablas.
Añadir un directorio compartido
En un post anterior escribí sobre el uso de DispatcherMiddleware y presenté una estructura básica de directorios. Ahora es el momento de agregar un directorio compartido, ver abajo.
|
|-- project
| |-- alembic
| | `--
| |
| |-- app_admin
| | |-- __init__.py
| | |-- blueprints
| | | |-- content_item
| | | | |-- forms.py
| | | | |-- __init__.py
| | | | `-- views.py
| | | |-- user
| | | | |-- forms.py
| | | | |-- __init__.py
| | | | `-- views.py
| | |-- templates
| | | |-- content_item
| | | | |-- content_items_list.html
| | | | `--
| | | |-- user
| | | | |-- users_list.html
| | | | `--
| | | |-- base.html
| | `-- translations
| | `-- es_ES
| | `-- LC_MESSAGES
| | |-- messages.mo
| | `-- messages.po
| |
| |-- app_frontend
| | |-- __init__.py
| | |-- blueprints
| | | |-- comments
| | | | |-- forms.py
| | | | |-- __init__.py
| | | | `-- views.py
| | | `-- demo_crud_view_uuid
| | | |-- forms.py
| | | |-- __init__.py
| | | `-- views.py
| | |-- templates
| | | |-- comments
| | | | |-- comment_form.html
| | | | `--
| | | `-- base.html
| | `-- translations
| | `-- es_ES
| | `-- LC_MESSAGES
| | |-- messages.mo
| | `-- messages.po
| |
| |-- shared
| | |-- blueprints
| | | |-- account
| | | | |-- forms.py
| | | | |-- __init__.py
| | | | `-- views.py
| | | `-- auth
| | | |-- forms.py
| | | |-- __init__.py
| | | `-- views.py
| | |-- static
| | | |-- blog
| | | |-- css
| | | |-- js
| | | |-- vendor
| | | `-- robots.txt
| | |-- templates
| | | |-- account
| | | | `-- overview.html
| | | |-- auth
| | | | `-- login.html
| | | |-- macros
| | | | `--
| | | `-- boxed.html
| | |
| | |-- constants.py
| | |-- class_app_mail.py
| | |-- class_input_validation.py
Los cambios en la carpeta estática
Ya tengo una variable de entorno que contiene el directorio estático. La razón es que el directorio estático está en un lugar diferente del sistema de producción. En el create_app() tanto del app_frontend como del app_admin creamos la aplicación Flask con la misma carpeta estática:
flask_static_folder = os.getenv('FLASK_STATIC_FOLDER')
app = Flask(
__name__,
static_folder=flask_static_folder,
)
Los cambios en las importaciones
Por supuesto que debemos hacer cambios en las importaciones iniciales. Por ejemplo, en los archivos app_frontend Python cambiamos las importaciones de los archivos Python desde:
# register blueprints
# authentication (shared)
from .blueprints.auth.views import auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/<lang_code>/auth')
a:
# register blueprints
# authentication (shared)
from shared.blueprints.auth.views import auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/<lang_code>/auth')
Cambios en template_folders
Esto es un poco mágico. No especifiqué un template_folder al crear la aplicación Flask , lo que significa que Flask utiliza la carpeta "plantillas" por defecto. ¿Pero cómo podemos acceder a las plantillas compartidas? Afortunadamente podemos especificar un directorio de 'plantillas' cuando creamos un Blueprint. Si especificas:
auth_blueprint = Blueprint('auth', __name__, template_folder='templates')
entonces le dices a Flask que hay un directorio 'templates' en el directorio 'auth' de Blueprint . Este directorio es relativo (!) al directorio 'auth' de Blueprint . La estructura del directorio entonces debería ser como esta:
| |-- shared
| | |-- blueprints
| | | `-- auth
| | | |-- forms.py
| | | |-- __init__.py
| | | |-- views.py
| | | `-- templates
| | | `-- auth
| | | |-- login.html
| | | `--
Tengan en cuenta que hay un directorio adicional 'auth' en el directorio de plantillas porque no queremos cambiar el archivo views.py. Este archivo contiene las funciones de visualización que terminan en:
...
return render_template(
'auth/login.html',
...
Si procedemos de esta manera obtenemos un directorio de plantillas para cada Blueprint compartido. no es realmente lo que queremos. El directorio de plantillas compartido debe tener la misma estructura que los directorios de plantillas app_frontend y app_admin , un único directorio con subdirectorios para cada Blueprint. Para lograr esto, cambiamos el Blueprint template_folder para que apunte a shared/templates:
auth_blueprint = Blueprint('auth', __name__, template_folder='../../templates')
Hacemos esto para todos los Blueprints compartidos y hemos terminado. Lo que es mágico de esto es que sólo tienes que hacer esto para un solo Blueprint compartido. Parece que Flask está construyendo una lista de directorios de búsqueda de plantillas y una vez que el template_folder para el 'auth' Blueprint ha sido procesado, la ruta se añade a esta lista y los otros Blueprints compartidos también encuentran sus plantillas. De la documentación de Flask : 'La carpeta de plantillas (Blueprint) se agrega a la ruta de búsqueda de plantillas pero con una prioridad menor que la carpeta de plantillas de la aplicación real'.
Esto funciona porque en este caso nos referimos a un único directorio de plantillas compartido, pero preferiría poder especificar una lista de ruta de búsqueda de plantillas a nivel de aplicación. Hay información disponible sobre cómo puede hacerlo, véase los enlaces que figuran a continuación.
Traducciones
Cuando se tiene un sitio multilingüe y se utiliza Babel se debe asegurar que las traducciones de app_frontend no sólo se generan desde el directorio app_frontend sino también desde el directorio compartido. Lo mismo se aplica a app_admin. Para lograr esto, añadimos el directorio al archivo babel_app_frontend.cfg:
[python: app_frontend/**.py]
[python: shared/**.py]
[jinja2: app_frontend/templates/**.html]
[jinja2: shared/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
encoding = utf-8
Resumen
Compartir archivos entre las aplicaciones Flask DispatcherMiddleWare hace tu trabajo mucho más fácil, sin duplicados. Tuve un poco de dificultad con las plantillas compartidas. Realmente debes tomarte un tiempo para entender esto. Al final creé una prueba y seguí el flujo en el código Jinja .
Compartir archivos no parecía tan difícil, empieza con una sólida estructura de directorios. Una vez más esto demuestra las capacidades de Flask. No es necesario hackear, ya está todo ahí.
Enlaces / créditos
Application Dispatching
https://flask.palletsprojects.com/en/1.1.x/patterns/appdispatch/
flask blueprint template folder
https://stackoverflow.com/questions/7974771/flask-blueprint-template-folder
Modular Applications with Blueprints
https://flask.palletsprojects.com/en/1.1.x/blueprints/#modular-applications-with-blueprints
Two Flask apps, frontend and admin, on one domain using DispatcherMiddleware
http://127.0.0.1:8000/en/blog/two-flask-apps-frontend-and-admin-on-one-domain-using-dispatchermiddleware
Leer más
DispatcherMiddleWare Flask
Recientes
- Cómo ocultar las claves primarias de la base de datos UUID de su aplicación web
- Don't Repeat Yourself (DRY) con Jinja2
- SQLAlchemy, PostgreSQL, número máximo de filas por user
- Mostrar los valores en filtros dinámicos SQLAlchemy
- Transferencia de datos segura con cifrado de Public Key y pyNaCl
- rqlite: una alternativa de alta disponibilidad y dist distribuida SQLite
Más vistos
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- Usando PyInstaller y Cython para crear un ejecutable de Python
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados
- Flask RESTful API validación de parámetros de solicitud con esquemas Marshmallow