Si ya terminaste de desarrollar tu proyecto web en el framework de Python, Django y ahora quiere entender que es lo que necesitas para subirlo a producción, en este tutorial te enseñare paso a paso a desplegar aplicaciones web creadas en Django gratuitamente usando un servicio llamado Railway.

Puedes seguir los mismos pases que se muestra en este tutorial para poder desplegar tanto proyectos de Django como proyectos de Django REST Framework

Requerimientos del Tutorial

  • Tener una cuenta en Railway
  • Tener Python instalado, de preferencia la version 3.9 o más novedosas

Creación del proyecto de Backend

Primero empecemos creando un proyecto en django:

mkdir django-railway
cd django-railway

Luego creemos un entorno virtual de Python, para esto puedes usar multiples paquetes como pipenv, conda, o virtualenv, entre otros. en mi caso estaré usando virtualenv.

Luego para crear el entorno virtual ejecuta:

python -m venv venv 

Activemos el entorno con:

activate 

o si estas en Linux

source ./venv/bin/activate  

Instalano Django y creando proyecto

pip install django
django-admin startproject django_project .

Ejecutemos el proyecto:

python manage.py runserver

Ahora creemos una aplicacion

django-admin startapp blog
django-admin startapp home

Añadamos estas aplicaciones en settings.py

en django_project/settings.py

INSTALLED_APPS = [
    "blog",
    "home"
]

ejecutemos las migraciones

python manage.py migrate

Añde algunas modificacione

blog/views.py

from django.shortcuts import render

# Create your views here.
def render_articles(request):
    return render(request, 'articles.html')

en blog/templates/articles.html

<h1>Article</h1>

blog/urls.py

from django.urls import path
from blog import views

urlpatterns = [
  path('', views.render_articles, name='articles'),
]

django_project/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("articles/", include("blog.urls")),
]

Ahora hagamos lo mismo en la aplicacion home

Creando Pagina Home

creemos un archivo templates/home.html

<h1>Home</h1>

<ul>
  <li>
    <a href="/">Home</a>
  </li>
  <li>
    <a href="/articles">Articles</a>
  </li>
</ul>

home/urls.py

from django.urls import path
from home import views

urlpatterns = [
    path("", views.render_home),
]

home/views.py

from django.shortcuts import render

# Create your views here.
def render_home(request):
    return render(request, "home.html")

Luego en django_project/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('home.urls')),
    path('articles/', include('blog.urls')),
]

Cargando Archivos esaticos

creemos un directorio estatico dentro de articles

blog/static/images/
blog/static/css/

colocar una imagen dentro de images, en mi caso colocare una imagen llamada fazt.png

Luego en settings añade lo siguiente:

STATICFILES_DIRS = [os.path.join(BASE_DIR,'blog/static'),]

y finalmente actuliza blog/templates/articles.html

{% load static %}

<h1>Article</h1>

<img src="{% static '/images/fazt.png' %}" style="width: 200px;">

Ahora vamos a desplegarlo

Primero creemos un archivo .gitignore, ignorando estos:

__pycache__
venv
db.sqlite3

runtime

añade un archivo runtime.txt con lo siguiente:

3.9.13

Puedes usar para obtener tu vrsion de Python

python --version 

Configura el Procfile

pip install gunicorn 

crea un archivo Procfile en tu proyecto con lo siguiente:

web: gunicorn django_project.wsgi

o tambien

web: gunicorn django_project.wsgi --log-file -

Crear archivo requeriments.txt

Luego generemos el archivo requirements.txt

pip freeze > requirments.txt

crea un commit

Luego creemos nuestro primer commit:

git add .
git commit -m "first commit"

Subelo a github, tambien puedes usar este comando si tines instalado github cli:

gh repo create django-railway --private --source=.

y luego puede subirlo usando

git push origin master

Subiendo a railway

Puedes subir a Railway usando tu cuenta de github y el repositorio que acabamos de crear

Primero crea un proyecto

selecciona tu repositorio

Añade una URL generada y Listo ya tienes desplegado tu proyecto.

es necesario tener en cuenta que Django 5.0 requiere una version superior a python 3.10, y tambien railway al estar basados en paquetes Nix solo soporta hasta la 11, asi que es necesario ver la version del paquete: https://nixpacks.com/docs/providers/python#setup

Solucionar Error ALLOWED_HOSTS

Ahora ya esta subido, pero si entramos en la URL obtendremos un error como este:

Invalid HTTP_HOST header: 'django-railway-production.up.railway.app'. You may need to add 'django-railway-production.up.railway.app' to ALLOWED_HOSTS.

esto significa que Django no permite a este dominio, pero podemos añadirlo

ALLOWED_HOSTS = ["django-railway-production.up.railway.app"]

si añades esto tambien tiene que añadir el localhost:

ALLOWED_HOSTS = ["localhost", "django-railway-production.up.railway.app"]

O si queremos que todos puedan acceder:

ALLOWED_HOSTS = ["*"]

Con esto si subes tus cambios con otro git push y esperas ahora veras tu sitio, pero no los archivos estaticos.

Archivos estaticos

si ejecutamos

python manage.py collectstatic 

este dara un error diciendo que necesitamos añadir el STATIC_ROOT, asi que hagamoslo, en settings.py, cerca de static_url añadir:

STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
STATIC_DIRS = [os.path.join(BASE_DIR, "static")] # esto ya lo habiamos añadido

Ahora Puedes volver a ejecutar la recoleccion de archivos estaticos:

python manage.py collectstatic 

esto creara una carpeta llamada statifiles en la raiz del proyecto donde colocara todos los archivos estaticos, como las imagenes o css y javascript que hubieramos añadido.

Ahora muchos hacen un commit de estos archivos, pero en realidad en lo personal prefiero generarlos cada vez que hago un despliegue para mantenerlos al dia y sin la nacesidad de hacer un tracking con git

asi que añade a tu .gitignore

staticfiles

y actualiza el Procfile a:

web: python manage.py collectstatic && gunicorn django_project.wsgi

Instalando Whitenoise

este es un modulo que permite servir a nuestras aplicaciones web nuestro propio contenido estatico

pip install whitenoise

In your settigs.py:

INSTALLED_APPS = [
    'django.contrib.staticfiles',
    'whitenoise.runserver_nostatic',
    "blog",
    "home"
]
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
]

este tambien pide que tengamos configurado variables como:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

pero como ya lo teniamos simplemente vamos a añadir esta:

STATICFILES_STORAGE="whitenoise.storage.CompressedManifestStaticFilesStorage"

no olvides ejecutar

pip freeze > requirments.txt

y subelo otra vez, y si esperas un poco a que se ectualice ahora podras ver tus contenidos estaticos terminados.

incluso puedes entrar al panel administrador, pero aun necesitamos crear un usuario asi que la pregunta es como ejecutas un comando en producción

CSRF

intenta hacer login en /admin

sin embargo puedes obtener un error de csrf trusted origins

asi que añade el dominio en settings.py

CSRF_TRUSTED_ORIGINS = ['http://*', 'https://django-railway-production.up.railway.app']

https://docs.djangoproject.com/en/4.0/ref/settings/#csrf-trusted-origins

Ejecutar migraciones en produccion

no such table: auth_user

necesitamos ejecutar las migraciones, atcualiza el procfile

web: python manage.py collectstatic && python manage.py migrate && gunicorn django_project.wsgi

pero antes de ejecutarlo vamos a crear una base de datos de postgresql reemplazando a sqlit3

Primero añadamos una base de datos

railway add

escogemos postgresql

para ver las variables

railway service
railway variables
pip install psycopg2

tenemos dos formas de conectarnos a la base de datos de postgres usando las variables o una cadena de coneccion, algo como esto:

DATABASES = {
    'default': {
        #'ENGINE': 'django.db.backends.sqlite3',
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': '',
        'USER': '',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
    }
}

en lo peronal prefiero la cadenas de conexion al ser más simple

pip install dj-database-url 
DATABASES = {
    'default': dj_database_url.config(default='postgresql://postgres:postgres@localhost:5432/mysite',        conn_max_age=600)}

en lo personal prefiero esto, al permitir usar sqlite en desarrollo y en produccion postgresql

DATABASES = {
    'default': dj_database_url.config(default='sqlite:///db.sqlite3')
}
pip freeze 

Añadiendo variables de entorno

pip intsall python-dotenv

from dotenv import load_dotenv

load_dotenv()

DATABASES = { 'default': dj_database_url.config(default=os.getenv('DATABASE_URL')) }

luego subir y finalmente podemos ejecutar

railway run python manage.py migrate
railway run python manage.py createsuperuser

Por cierto, ejecutando el comando de railway me encontrado con este error Unable to parse config file, regenerating al ejecutar, esto asi que si quieres solucionar tienes que eliminar el archivo .railway/config.json

Listo con esto ya lo tenemos en producción.

Ejecutar comandos railway

instalar railway cli

npm i -g @railway/cli

Para autenticarse con Railway desde consola

railway login

esto abrira una pestaña del navegador.

Ahora enlacemos nuestro proyecto local con nuestro proyecto remoto creado con railway:

railway link
railway run python --version
railway run python manage.py createsuperuser

esto creara un usuario tanto en local como en remoto, ahora intenta hace un login.

Añadamos Django REST Framework

pip install djangorestframework
django-admin startapp tasks

añadamos la aplicacion de tareas en settings

INSTALLED_APPS = [
    ...
    "whitenoise.runserver_nostatic",
    "blog",
    "home",
    "tasks",
    "rest_framework",
]

creemos un modelo en tasks/models.py

class Task(models.Model):
    title = models.CharField(max_length=200)
    completed = models.BooleanField(default=False, blank=True, null=True)

    def __str__(self):
        return self.title

luego en ejecuta

python manage.py makemigrations
python manage.py migrate

añade en admin lo siguiente, para poder crear tareas desde el administrador

from django.contrib import admin
from .models import Task

# Register your models here.
admin.site.register(Task)
python manage.py runserver

Creando API

crear un archivo tasks/serializers.py

from rest_framework import serializers
from .models import Task

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = '__all__'

luego desde tasks/views.py

from rest_framework import viewsets
from .models import Task
from .serializers import TaskSerializer

# Create your views here.
class TaskList(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

Luego desde tasks/urls.py

from rest_framework import routers
from django.urls import path, include
from . import views

router = routers.DefaultRouter()
router.register(r'tasks', views.TaskList, basename='tasks')

urlpatterns = router.urls

finalmente en django_project/urls.py

urlpatterns = [
    ...
    path("api/", include("tasks.urls")),
]

visita http://localhost:8000/api/tasks

ejecuta pip frezee y despliegalo

finalmente puedes usar alguna web como httpie.io para poder probar la aplicacion REST

Recomendaciones Finales

  • Siempre configurar variables de entorno para evitar colocar datos sensibles y reutilizar configuraciones
  • usar un administrador de paquetes como pipenv o poetry

subir a Railway

https://stackoverflow.com/questions/73825257/railway-app-cant-find-staticfiles-after-deployment-django-app

de SQlite a Otra base de datos

vamos a cambiar de sql a postgresql:

https://stackoverflow.com/questions/20532504/django-app-deployment-with-sqlite-database

https://dev.to/osahenru/using-railway-app-to-deploy-your-django-project-3ah1

Más Recursos

https://youtu.be/NUqtNglEcCU

https://youtu.be/6wO9OfUNdjY

https://dev.to/mr_destructive/django-postgresql-deployment-on-railway-app-d54

https://stackoverflow.com/questions/73843151/how-to-run-commands-in-cli-with-railway-app

https://medium.com/codex/deploying-react-through-djangos-static-files-part-1-dev-setup-8a3a7b93c809