De código duplicado a código limpio en Django + IA

Mantener un proyecto Django limpio, legible y modular no es una tarea menor. Si alguna vez te has perdido entre vistas duplicadas, funciones interminables o clases que hacen demasiado, sabes de qué hablo.

En mis proyectos —especialmente al desarrollar una tienda en línea con múltiples tipos de productos y pagos— descubrí que el verdadero “Clean Code” en Django empieza por una sola palabra: modularización.

¿Qué significa escribir código limpio en Django

Principios base: claridad, modularidad y mantenimiento

El código limpio no se trata de escribir bonito; se trata de escribir código que otras personas (incluido tú en seis meses) puedan entender sin esfuerzo.
En Django, eso se traduce en organizar el proyecto por módulos coherentes, mantener las vistas pequeñas y reutilizar la lógica común.

Por qué el “Clean Code” no es solo estética

Un proyecto Django mal estructurado puede funcionar igual, pero será difícil de probar, escalar y mantener. Escribir código limpio implica tomar decisiones de diseño que reduzcan el acoplamiento, eviten la repetición y sigan principios como DRY (Don’t Repeat Yourself) o KISS (Keep It Simple, Stupid).

Modularización: la base del código limpio en Django

De vistas basadas en funciones a vistas basadas en clases (CBV)

Este articulo habla sobre la importancia de la modularización y sobre el uso de las clases como piezas claves; como bien saber, en Django, para definirlo las vistas, tenemos dos formas, una de ellas es mediante métodos:

def post_list(request):
    posts = Post.objects.all().order_by('-id')
    return render(request, 'post_list.html', {'posts': posts})

Funciona, sí, pero escala mal. Si necesitas comportamiento compartido (por ejemplo, filtrar, ordenar o aplicar permisos), terminas copiando código.

La alternativa —y mi recomendación personal— es usar Class-Based Views (CBV), que permiten heredar comportamiento y centralizar la lógica común:

class PostListView(ListView):
    model = Post
    template_name = 'post_list.html'      # por defecto: app/post_list.html
    context_object_name = 'posts'         # por defecto: object_list
    ordering = ['-id']

El simple hecho de heredar de ListView reduce líneas de código, mejora la legibilidad y te da una estructura predecible.

Como recomendación, siempre se deben de emplear las Vistas Basadas en Clases cada vez que sea posible; personalmente pienso que las clases son de las mejoras características que tenemos en cualquier lenguaje de programación, ya que, son pieza clave en la modularización de nuestras aplicaciones.

Ejemplo práctico: refactorizando una vista de pagos en Django

En mi caso, tenía dos vistas muy parecidas: una para pagar libros y otra para productos.
Ambas recibían los mismos parámetros (order_id, book_id o product_id, type) y realizaban prácticamente la misma lógica.

Suponte la siguiente clase:

class PaymentBookView(LoginRequiredMixin, View, BasePayment):
    def __init__(self):
        # super().__init__()
        BasePayment.__init__(self)
        
    def _redirect_or_json(self, request, url_name, **kwargs):
        url = reverse(url_name, kwargs=kwargs)
        if request.headers.get("Content-Type") == "application/json":
            return JsonResponse({"redirect": url})
        return redirect(url, **kwargs)
    
    def get(self, request, order_id:str, book_id:int, type:str):
        return self._process(request, order_id, book_id, type)
    
    def post(self, request, order_id:str, book_id:int, type:str):
        return self._process(request, order_id, book_id, type) 
    
    def _process(self, request, order_id:str, book_id:int, type:str):
        #TODO revisar que NO compre el mismo producto 2 veces
     
        # si la ordenID en la URL no es valida, lo busca en el request, caso Stripe   
        # http://127.0.0.1:8000/store/payment/orderID/2/stripe?order_id=cs_test_a***pR
        if order_id == 'orderID':
            ***
     
        # procesamos la orden
        response = self.process_order(order_id, type)

        # Error en la orden
        if response == False:
            return self._redirect_or_json(request, "s.payment.error", message_error=self.message_error)
        
        #usuario auth
        user = request.user 
        
        # buscamos el producto
        try:
            book = Book.objects.get(id=book_id)
        ***

        # creamos el producto si todo esta ok
        payment = Payment.objects.create(
            user=user,
            type=self.type,  
            coupon=None,  
            orderId=order_id,
            price=self.price,
            trace=self.trace,  
            content_type=ContentType.objects.get_for_model(book),
            object_id=book.id
        )

        return self._redirect_or_json(request, "s.payment.success", payment_id=payment.id)

La cual, forma parte del curso y libro completo para crear una tienda en línea en Django; se han obviado algunas implementaciones pero, lo esencial lo puedes ver en el código anterior; el problema es que, definir hay mucho código repetido en la clase anterior, cuyas rutas son:

# pagar un producto/book
path('payment/<str:order_id>/<int:book_id>/<str:type>', PaymentBookView.as_view(), name='s.payment'),
# pagar un producto
path('product/payment/<str:order_id>/<int:product_id>/<str:type>', PaymentProductView.as_view(), name='s.product.payment'),

Que en definitiva, lo que se esta indicando como parámetros dignos es el identificador de unos productos como lo son un libro y algo llamado producto como elementos comprables; pero, la lógica es la misma; como podemos optimizar esto para que realmente sea una clase modular, muy fácil, para ello, podemos crear otra clase, que tenga este comportamiento común, pero, como hacemos con los parámetros:

content_type=ContentType.objects.get_for_model(product),
   object_id=product.id
)

Solución: una clase base que lo unifica todo

La clave fue crear una clase base que centralizara el comportamiento común y dejara solo los detalles específicos a cada modelo:

class BasePaymentView(LoginRequiredMixin, View, BasePayment):
    model = None          # el modelo (Book o Product)
    lookup_field = "id"   # campo de búsqueda (generalmente id)
    url_kwarg = None      # el parámetro en la URL (book_id, product_id)

    def __init__(self):
        BasePayment.__init__(self)

    def _redirect_or_json(self, request, url_name, **kwargs):
        url = reverse(url_name, kwargs=kwargs)
        if request.headers.get("Content-Type") == "application/json":
            return JsonResponse({"redirect": url})
        return redirect(url, **kwargs)

    def get(self, request, order_id: str, type: str, **kwargs): # parametro extra (**kwargs) que es el de <int:product_id>/<int:book_id>
        return self._process(request, order_id, type, **kwargs) 

    def post(self, request, order_id: str, type: str, **kwargs): # parametro extra (**kwargs) que es el de <int:product_id>/<int:book_id>
        return self._process(request, order_id, type, **kwargs)

    def _process(self, request, order_id: str, type: str, **kwargs):
        # si la ordenID en la URL no es valida, lo busca en el request (caso Stripe)   
        if order_id == 'orderID':
            ***
     
        # procesamos la orden
        response = self.process_order(order_id, type)
        if response is False:
            return self._redirect_or_json(request, "s.payment.error", message_error=self.message_error)

        # usuario auth
        user = request.user 

        # buscamos el objeto parametro extra <int:product_id>/<int:book_id>
        pk = kwargs.get(self.url_kwarg)
        obj = get_object_or_404(self.model, **{self.lookup_field: pk})

        # creamos el payment
        payment = Payment.objects.create(
            user=user,
            type=self.type,
            coupon=None,
            orderId=order_id,
            price=self.price,
            trace=self.trace,
            content_type=ContentType.objects.get_for_model(obj),
            object_id=obj.id
        )

        return self._redirect_or_json(request, "s.payment.success", payment_id=payment.id)

Como puedes apreciar, esta nueva clase hereda todo el comportamiento en común de las dos anteriores pero, la parte dinámica se maneja mediante atributos, para especificar exactamente lo que se quiere comprar:

# Procesa la compra de un Libro
class PaymentBookView(BasePaymentView):
    model = Book
    url_kwarg = "book_id"

# Procesa la compra de un Producto
class PaymentProductView(BasePaymentView):
    model = Product
    url_kwarg = "product_id"

De esta forma tan sencilla, logramos modularizar este componente de nuestra aplicación; en la formación anterior, nos esforzamos en hacer un todo sea lo mas modular posible, una tienda en línea son de los desarrollos que se pueden salir fácilmente de control, al tener que manejar muchas cosas como pasarelas de pago, parámetros de configuración, pantallas de errores, registros y vale contar, y es por eso que en esta formación, te presentamos un enfoque eficiente y modular que podrás emplear no solo en proyectos en Django con Python web si no, en todo tipo de proyectos.

Cómo adaptarlo para otros modelos

Si mañana quisiera agregar un nuevo tipo de pago (por ejemplo, membresías), solo necesitaría una nueva clase que especifique su modelo.
Esto reduce drásticamente el código duplicado y hace que la lógica de negocio sea más fácil de mantener y probar.

Principios de Clean Code aplicados a Django

DRY, KISS y YAGNI en la práctica

  • DRY: Evita repetir código. Si copias y pegas una lógica dos veces, conviértela en un método o clase reutilizable.
  • KISS: Mantén las vistas simples y con un propósito claro.
  • YAGNI: No implementes cosas “por si acaso”. Django ya tiene mucho resuelto por ti.

Nombrado, PEP8 y consistencia en tus vistas y modelos

  • Sigue las normas de estilo PEP8 y usa nombres descriptivos. Una clase llamada PaymentBookView comunica mucho más que BookPay.
  • La consistencia visual (orden de imports, espaciado, comentarios) no es trivial: reduce el esfuerzo cognitivo al leer el código.

Cómo mantener tu proyecto Django escalable y limpio

Testing, refactorización continua y revisiones de código

El código limpio no se escribe una sola vez: se mantiene.
En mis proyectos, acostumbro a hacer pequeñas refactorizaciones después de cada módulo nuevo. Un test que falla suele ser una oportunidad de limpiar.

Uso de mixins, utils y librerías personalizadas

Agrupa la lógica transversal (autenticación, permisos, validaciones) en mixins o módulos utils.py.
Esto evita que tus vistas crezcan sin control y mejora la trazabilidad de errores.

Conclusión

La modularización no solo mejora la legibilidad: reduce el estrés del mantenimiento.
Cada clase o módulo debería tener una responsabilidad clara y un nombre que lo exprese.
En mi experiencia, las aplicaciones Django que siguen este enfoque escalan mejor y requieren menos tiempo de depuración.

El código limpio no es una meta final, es un proceso constante de mejora.
Y Django —por su estructura basada en clases, señales y apps independientes— es el entorno perfecto para aplicarlo.

En resumen:

  • La modularización y reutilización son claves en proyectos grandes.
  • La IA puede ser un excelente apoyo, siempre que nosotros tengamos claro qué queremos conseguir.
  • La solución más sólida es la que sacrifica un poco de flexibilidad para ganar en seguridad y claridad.
  • Así que nada, este fue el contexto que quería darte para YouTube. Espero que disfrutes de esta clase gratuita, que forma parte del curso completo. Nos vemos en la siguiente sección.

Preguntas frecuentes (FAQ)

  • ¿Qué principios de Clean Code se aplican mejor a Django?
    Principalmente DRY, KISS y la responsabilidad única. Cada vista o modelo debe tener un solo propósito claro.
  • ¿Cuándo conviene usar vistas basadas en clases?
    Siempre que necesites comportamiento reutilizable o extensible: paginación, permisos, o filtros dinámicos.
  • ¿Cómo detectar que tu proyecto Django necesita refactorización?
    Si te cuesta modificar algo sin romper otra parte, o si ves el mismo bloque de código en más de dos archivos, es momento de limpiar.

Acepto recibir anuncios de interes sobre este Blog.

Refactorizando como un PRO en Django: limpia tu código modularizando.

| 👤 Andrés Cruz

🇺🇸 In english