Desplegar un proyecto de Django en PythonAnywhere

Desplegar un proyecto de Django en PythonAnywhere

PythonAnywhere es una empresa de hosting para aplicaciones web escritas en Python. Al crear un usuario, conseguimos de forma gratuita una especie de máquina virtual Linux con varios intérpretes de Python instalados, múltiples módulos y paquetes de terceros (entre ellos, Django) y la capacidad de instalar nuevos vía pip, una base de datos MySQL lista para usar, un servidor web plenamente configurado, un sistema de archivos con 512 MB de capacidad y muchas otras cosas interesantes. No solamente se trata de una solución ideal para llevar por primera vez una aplicación web a producción, sino también para proyectos profesionales. PythonAnywhere permite seleccionar únicamente los recursos que queremos usar y pagar en consecuencia; y, en los planes pagos, configurar nuestras aplicaciones con un dominio propio.

Para seguir esta guía, te recomendamos trabajar con este proyecto de Django, que es el que usaremos como ejemplo y estaremos subiendo a PythonAnywhere. Es un simple sitio web con una vista que permite dejar mensajes en una suerte de muro digital. El nombre de este proyecto de ejemplo es myproject y el de la aplicación myapp (en términos de Django). Una vez entendido el procedimiento, podrás configurar y subir tus propios proyectos de Django, que tal vez difieran un poco en su estructura.

1. Registro y configuración

Para comenzar a utilizar PythonAnywhere, nos dirigimos a https://www.pythonanywhere.com/pricing/ y presionamos el botón Create a Beginner account:

Crear cuenta en PythonAnywhere

Luego nos registramos completando el formulario con un nombre de usuario, una dirección de correo electrónico y una contraseña. Esto enviará un correo electrónico a la dirección indicada para confirmar el registro. Una vez hecho esto, ¡listo! Ya tenemos una cuenta gratuita en PythonAnywhere para subir el código de nuestro proyecto de Django.

Ahora, al ingresar a https://www.pythonanywhere.com/ deberíamos ver algo así:

Dashboard de PythonAnywhere

Tenemos que indicarle a PythonAnywhere que queremos subir una aplicación de Django. En el menú superior derecho, vamos a dirigirnos a la opción Web. Una vez allí, a la izquierda, presionamos el botón

Botón para crear nueva aplicación web

Se iniciará un asistente que nos consultará el framework que queremos usar, la versión de Python y el nombre del proyecto. Completaremos esos datos con las opciones «Django», «Python 3.10» (o la versión que estés utilizando) y «myproject» (o el nombre de la carpeta que contenga tu archivo manage.py), como se ilustra a continuación.

Crear aplicación en PythonAnywhere

El asistente tardará unos segundos en generar nuestra aplicación. Al finalizar, veremos esto:

Aplicación creada en PythonAnywhere

En rojo está marcada la dirección de URL (terminada en .pythonanywhere.com) en la que ya va a estar corriendo nuestra recién creada aplicación. PythonAnywhere también provee la posibilidad de configurar un dominio propio en los planes pagos.

2. Configuración de un proyecto para producción

Ya tenemos una aplicación corriendo en la web en la dirección nombreusuario.pythonanywhere.com. Ahora queremos que allí corra nuestra aplicación, en lugar de la generada automáticamente por el asistente de PythonAnywhere. Una vez hecho esto cualquier persona con acceso a la dirección de URL correspondiente podrá ingresar a través de su navegador web. Para garantizar la seguridad de nuestra aplicación, debemos asegurarnos de que la configuración sea la adecuada para un ambiente de producción.

Recordemos que la variable DEBUG definida en settings.py indica si el proyecto corre en modo depuración (True) o no (False). Cuando el modo de depuración está activado, Django relaja algunas políticas de seguridad: por ejemplo, muestra extractos del código cuando ocurre una excepción. Este tipo de situaciones deben ser evitadas en un ambiente de producción. Por ende hay que asegurarse de que en el archivo figure:

DEBUG = False

Cuando el modo depuración está deshabilitado, Django exige que se configure la lista que se encuentra inmediatamente a continuación:

ALLOWED_HOSTS = []

Los elementos de ALLOWED_HOSTS elementos deben indicar las direcciones de IP o dominios en los que se sirve la aplicación web en el ambiente de producción. En nuestro caso, colocaremos únicamente el subdominio que PythonAnywhere ha creado para nosotros. Por ejemplo:

ALLOWED_HOSTS = ["cpeit.pythonanywhere.com"]

(Recordá reemplazar “cpeit” por tu nombre de usuario).

Eso es todo por ahora. Más adelante cambiaremos otros aspectos de la configuración.

3. Subir un proyecto

Hemos configurado nuestro proyecto para que sea apto para correr en producción. Ahora debemos subir todo el árbol de carpetas de nuestro myproject a PythonAnywhere. En la interfaz de PythonAnywhere, vamos al menú File en el extremo superior derecho de la pantalla. A la izquierda veremos lo siguiente:

Carpetas en PythonAnywhere

Lo que nos interesa aquí es la carpeta myproject, donde reside el código de la aplicación generada por PythonAnywhere. La eliminamos, ya que el paso siguiente será subir nuestra propia carpeta myproject que tenemos en el ambiente de desarrollo (esto es, la computadora de cada uno de nosotros).

Eliminar carpeta en PythonAnywhere

Puesto que no es posible subir una carpeta entera a través del navegador, debemos comprimir la carpeta de nuestro proyecto en formato ZIP con un programa como 7-Zip, WinRAR o similar. Sugerimos hacerlo del siguiente modo para evitar problemas más adelante.

Comprimir proyecto de Django

De vuelta en el sitio de PythonAnywhere, presionamos el botón naranja Upload a file, seleccionamos myproject.zip en la ubicación correspondiente y aceptamos.

Subir proyecto a PythonAnywhere

Resta descomprimir el archivo. Presionamos el botón Open Bash console here, que abrirá una terminal remota.

Abrir terminal en PythonAnywhere

Una vez cargada la terminal, escribimos el siguiente comando para extraer nuestro proyecto: unzip myproject.zip. Rápidamente extraerá todo el contenido del archivo y al final cerramos la terminal presionando CTRL + D.

Terminal en PythonAnywhere

¡Listo! Ya subimos el código de nuestro proyecto. Por último, le indicamos a PythonAnywhere que debe recargar el código de la aplicación que está corriendo. Vamos nuevamente a la pestaña Web y presionamos el botón Reload nombre_usuario.pythonanywhere.com:

Botón recargar en PythonAnywhere

Visitamos nuestro dominio y voilá:

Aplicación web de Django en PythonAnywhere

¡Felicitaciones! Hemos desplegado nuestra primera aplicación de Django.

4. Archivos estáticos

Un archivo es estático cuando su contenido no es generado en tiempo real por Django ni por cualquier otro código de Python (imágenes, archivos de CSS y JavaScript, etc.), a diferencia, por ejemplo, de las plantillas HTML, que son procesadas en cada petición. Por defecto, cuando el navegador web solicita el contenido de un archivo estático (p. ej. /static/myapp/styles.css), Django lo busca en el sistema de archivos, lo abre, lo lee, y lo retorna como contenido de una respuesta HTTP. Esto está bien durante el desarrollo, pero en la etapa de producción es mejor que el servidor web (Apache, NGINX, o el que sea que esté configurado) se ocupe de eso, cosa que podrá hacer más rápido y con mayor eficiencia. No solo es mejor, sino necesario. De hecho, Django solamente sirve los archivos estáticos cuando el modo depuración está activado.

PythonAnywhere por defecto sirve los archivos estáticos dentro de la carpeta static de nuestro proyecto. Obsérvese en la pestaña Web:

Rutas de los archivos estáticos en PythonAnywhere

Según esto, nuestro archivos estáticos deberían estar en /home/nombre_usuario/myproject/static/, pero en realidad están alojados en /home/nombre_usuario/myproject/myapp/static/ (o sea, dentro de nuestra aplicación, no de nuestro proyecto). Django provee un comando para copiar automáticamente los archivos de un directorio a otro en producción, que deberemos ejecutar siempre que hagamos cambios en alguno de ellos.

Antes de llegar ahí, hay que configurar en nuestro proyecto la ruta de la carpeta en la que PythonAnywhere estará buscando los archivos estáticos. Abrimos nuestro archivo local settings.py y al final buscamos:

STATIC_URL = 'static/'

Debajo de esta línea, agregamos (¡no reemplazamos!) la variable STATIC_ROOT con la ruta marcada en rojo en la imagen anterior:

STATIC_ROOT = '/home/cpeit/myproject/static'

(Nuevamente, recordá reemplazar “cpeit” por tu nombre de usuario).

En PythonAnywhere vamos a la pestaña Files, navegamos a la carpeta myproject/myproject/ y subimos la versión actualizada de settings.py. Luego, nos dirigimos a la carpeta anterior (o sea, /home/nombre_usuario/myproject/, donde se encuentra el manage.py) y presionamos el botón Open Bash console here. Cuando se haya abierto la terminal, ejecutamos:

python manage.py collectstatic

Puesto que hicimos cambios en settings.py, es necesario recargar la aplicación yendo a la pestaña Web y presionando el botón Reload. Hecho esto, PythonAnywhere ya será capaz de servir los archivos estáticos de nuestro proyecto (nótese el logo):

Aplicación web de Django en PythonAnywhere con archivos estáticos

5. Base de datos MySQL

Por defecto las aplicaciones de Django usan una base de datos de SQLite. SQLite es un motor de base de datos muy útil para la etapa de desarrollo: no es necesario instalar software adicional y está muy bien integrado con Python. No obstante, es poco apto para la etapa de producción. Las bases de datos de SQLite son simplemente archivos en el sistema, y como tales tienen ciertas limitaciones: dos procesos no pueden escribir simultáneamente en un mismo archivo. Además, en los servicios de hosting el acceso al sistema de archivos suele ser una operación más pesada que su equivalente consulta a la base de datos.

En términos generales, MySQL es una buena solución para el ambiente de producción y PythonAnywhere provee soporte gratuito para este motor de base de datos. Por ende, hay que configurar nuestra aplicación para que haga uso de él. Para conseguirlo, primero tenemos que crear un servidor de bases de datos MySQL en el hosting. En Pythonanywhere, vamos a la pestaña Databases y elegimos una contraseña:

Iniciar servidor de MySQL en PythonAnywhere

Presionamos el botón Initialize MySQL y esperamos unos segundos hasta que se complete el proceso. Una vez finalizado, veremos algo como lo siguiente:

Información de MySQL en PythonAnywhere

Las dos primeras filas indican la dirección donde se encuentra alojado nuestro servidor de bases de datos MySQL (que tiene la estructura nombre_usuario.mysql.pythonanywhere-services.com) y el nombre de usuario de dicho servidor (que no es otro que nuestro usuario de PythonAnywhere). En la tercera fila, vemos un enlace con el nombre de la base de datos creada por defecto (nombre_usuario$default).

Recordando esta información, efectuemos los cambios correspondientes en settings.py para que el proyecto empiece a trabajar con MySQL y los datos de PythonAnywhere en particular. Ubiquemos en dicho archivo la siguiente sección:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

Esta configuración de una base de datos SQLite es generada automáticamente por el comando python manage.py startproject, lo que permite empezar a trabajar con Django rápidamente sin tener que ocuparnos de la inicialización y configuración de un motor de base de datos. Ya es hora de indicar que se trata de una base de datos MySQL:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': 'cpeit.mysql.pythonanywhere-services.com',
        'USER': 'cpeit',
        'PASSWORD': '<tu-contraseña-aquí>',
        'NAME': 'cpeit$default',
        'CHARSET': 'utf8',
    },
}

Nótese que los valores en las claves 'HOST', 'USER' y 'NAME' corresponden a las tres filas de información que mostramos en la imagen anterior. Cambiá siempre «cpeit» por tu nombre de usuario y «tu-contraseña-aquí» por la contraseña que hayas indicado al inicializar el servidor de bases de datos en PythonAnywhere.

Lista la configuración. Ahora subimos nuevamente el recién actualizado settings.py a PythonAnywhere. Dado que cambiamos la base de datos, debemos aplicar las migraciones. Abrimos una terminal en /home/nombre_usuario/myproject/ (¡en PythonAnywhere!) y ejecutamos:

python manage.py migrate

Esto definirá las tablas de todas las aplicaciones configuradas en nuestro proyecto, incluidas, por supuesto, aquellas derivadas de los modelos en myapp/models.py.

Por último, pero no menos importante, vamos a la pestaña Web y recargamos vía el botón Reload para que el dominio empiece a trabajar con la nueva base de datos. Puesto que acabamos de crear la base de datos, cuando ingresemos nuevamente a nuestro sitio no veremos ningún mensaje cargado:

Aplicación web de Django con MySQL

Configuración adicional para desarrollo

Los varios cambios que hemos realizado hasta ahora para hacer el proyecto apto para producción han tenido como contraparte que ya no podamos ejecutarlo en desarrollo. Esto no debería ser ningún problema; por lo general, los proyectos de Django tienen doble configuración: una para cada etapa. Vamos a crear un nuevo archivo llamado settings_dev.py (junto a settings.py) donde pondremos la configuración específica para el ambiente de desarrollo que difiere de la de producción. Recordemos los valores que cambiamos en las secciones anteriores:

DEBUG
ALLOWED_HOSTS
DATABASES
STATIC_ROOT

Vayamos, entonces, al nuevo archivo para reestablecer esa configuración a sus valores de desarrollo:

# settings_dev.py
DEBUG = True
ALLOWED_HOSTS = []
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
STATIC_ROOT = None

Ahora importaremos esta configuración cuando el archivo exista. Vamos al final de settings.py y agregamos:

try:
    # Importar la configuración de desarrollo cuando el archivo esté
    # disponible.
    from settings_dev import *
except ModuleNotFoundError:
    # Si no está disponible, es porque se está corriendo en producción.
    pass

La lógica de este código es justamente la que indican los comentarios. El archivo settings_dev.py nunca lo subimos a PythonAnywhere. No obstante, cuando ejecutemos python manage.py runserver de forma local, el archivo estará presente y las variables configuradas en él reemplazarán a las que están definidas previamente en settings.py, de ahí que hayamos importado el módulo al final del archivo y no al comienzo como se acostumbra.

Otras alternativas más flexibles para tener dos o más configuraciones en un mismo proyecto de Django las hemos explicado en este artículo.

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

2 comentarios.

    • Recursos Python says:

      Hola, ¿estás usando el proyecto de ejemplo? ¿No tendrás habilitada la opción de proteger con contraseña de PythonAnywhere?

Deja una respuesta