Escribir CSV con Django

Video thumbnail

Trabajar con archivos CSV en Django puede ser tan simple como usar un csv.reader… o tan frustrante como recibir un Excel completamente roto de un cliente. A lo largo del tiempo he tenido que exportar datos de clientes directamente desde la base de datos y también he tenido que lidiar con ese Excel horrible que todos hemos sufrido alguna vez. Así que en este artículo voy a explicarte todo lo que necesitas saber para manejar CSV en Django sin perder la paciencia.

Qué es un archivo CSV y por qué Django lo maneja tan bien

El CSV (Comma Separated Values) es uno de los formatos más simples y útiles para representar datos tabulares. Django interactúa de maravilla con él porque:

  • Es texto plano
  • Funciona perfecto con Python y el módulo csv
  • Es ligero, rápido y no requiere dependencias externas

Ventajas del formato CSV para bases de datos

  • Fácil de generar desde Django.
  • Perfecto para importar registros en masa.
  • Compatible con cualquier sistema: Excel, Google Sheets, bases de datos, ERPs.

Preparar tus archivos antes de trabajar con CSV en Django

Cómo convertir correctamente un Excel a CSV

La conversión parece simple: abrir en Excel → Guardar como CSV.
Pero en la práctica hay que considerar:

  • Codificación UTF-8
  • Separador correcto (coma o punto y coma)
  • Limpieza de filas vacías
  • Que estén con datos ordenados listo para procesar.
  • Manejo de archivos grandes
    • Usa StreamingHttpResponse si el archivo supera los miles de registros.
  • Evitar duplicados y asegurar integridad
    • get_or_create() es tu mejor aliado.
  • Encoding recomendado para evitar errores
    • UTF-8 sin BOM → la opción segura.

Validar encabezados, separadores, encoding y newline

Antes de importar un CSV a Django, revisa:

  • Que las columnas tengan nombres claros y sin espacios raros
  • Que los separadores sean homogéneos
  • Que el archivo use newline correcto (newline="")
  • Que no existan tildes rotas o caracteres fantasma

Errores típicos al recibir archivos de clientes

Cuando recibí aquel Excel imposible, descubrí errores como:

  • Columnas mezcladas
  • Encabezados repetidos
  • Fechas con varios formatos
  • Filas con valores “invisibles”

Cómo importar CSV a modelos Django paso a paso

 Estructurar tu modelo para una importación limpia

Un modelo típico podría verse así:

class Cliente(models.Model):
   nombre = models.CharField(max_length=150)
   email = models.EmailField()
   fecha_registro = models.DateField()

Usar csv.reader y DictReader

  • csv.reader: trabaja con listas
  • csv.DictReader: trabaja con diccionarios usando encabezados del CSV

Guardar registros con objects.create() y get_or_create()

Ejemplo:

with open("clientes.csv") as file:
   reader = csv.DictReader(file)
   for row in reader:
       Cliente.objects.get_or_create(
           email=row["email"],
           defaults={
               "nombre": row["nombre"],
               "fecha_registro": row["fecha_registro"]
           }
       )

Validación de datos: fechas, decimales y campos vacíos

Cuando procesé datos reales de clientes, me topé con fechas como 12/1/23, 2023-01-12 y hasta “enero 2023”.
Mi regla: normaliza antes de guardar.

Importar CSV desde una vista en Django (ejemplo práctico)

Subir archivo CSV desde un formulario

Una forma directa:

<form method="POST" enctype="multipart/form-data">
   {% csrf_token %}
   <input type="file" name="archivo">
   <button type="submit">Importar</button>
</form>

Procesar filas y manejar errores

def importar_csv(request):
   if request.method == "POST":
       archivo = request.FILES["archivo"]
       data = archivo.read().decode("utf-8").splitlines()
       reader = csv.reader(data)
       next(reader)  # saltar encabezados
       for fila in reader:
           Cliente.objects.create(
               nombre=fila[0],
               email=fila[1],
               fecha_registro=fila[2]
           )
   return render(request, "importar.html")

Cómo exportar datos a CSV en Django

A mí, por ejemplo, me tocó exportar datos de clientes para enviar reportes personalizados. Y justo después tuve que tomar un Excel completamente desordenado del cliente, convertirlo a CSV y procesarlo desde Django. CSV es esa herramienta que siempre te saca del apuro.

Para escribir archivos, se usa el mismo esquema que los códigos anteriores, pero se deben de abrir los archivos en modo escritura y usar la función de writer() en lugar de la de reader(); también es importante señalar, que el archivo a escribir no tiene que existir (en el siguiente ejemplo, para evitar eliminar el archivo anterior, se coloca al archivo a referenciar como “Libro2”):

def csv_write(request):
    filename="documents/Libro2.csv"
    try:
        file = open(filename, 'w', newline="")
        #print(type(file))
        csv_writer = csv.writer(file, delimiter=",")
        # print(type(csv_writer))

        csv_writer.writerow(["Movie 1","Movie 2", "Movie 3"])
        csv_writer.writerow(["Avengers","Batman", "Superman"])
        csv_writer.writerow(["Avengers 3","Batman 2", "Other"])
        csv_writer.writerow(["Avengers 4","Batman", "Spiderman"])

        file.close()
    except (IOError) as error:
        print("Error {}".format(error))
    
    return render(request, 'csv.html')

Al estar en modo escritura, se debe de presentar una estructura la cual queremos replicar en el archivo; es este caso, serían listas, que como vimos en los ejemplos de lectura, son los formatos devueltos al momento de leer los archivos.

Para escribir una lista, se usa la función de writerow(), también puedes usar la de writerows() para escribir múltiples listas:

data=[
    ["Name","Surname","Age"],
    ["Jon","Snow",33],
    ["Daenerys","Targaryen",25],
    ["Tyrion","Lannister",40],
    ["Jaime","Lannister",35],
    ["Cersei","Lannister",36]
]

writer.writerows(data)

También se usa la opción de newline como un vacío, con esto, evitamos que cuando se vaya a escribir una nueva columna, coloque un espacio entre registros; sin definir el newline:

Movie 1;Movie  2;Movie  3

Avengers;Batman;Superman

Avengers 3;Batman 2;Other
Definiendo el newline:
Pelicula 1;Pelicula 2;Pelicula 3
Avengers;Batman;Superman
Avengers 3;Batman 2;Otro
Su ruta:
csvs\urls.py
urlpatterns = [
    // ***
    path(csv_write, views.csv_write),

Y tendremos como salida, un archivo con el formato que especificamos anteriormente:

documents\Libro2.csv

Pelicula 1;Pelicula 2;Pelicula 3
Avengers;Batman;Superman
Avengers 3;Batman 2;Otro
Avengers 4;Batman;Spiderman

Otro ejemplo:

def exportar_clientes(request):
   response = HttpResponse(content_type='text/csv')
   response['Content-Disposition'] = 'attachment; filename=clientes.csv'
   writer = csv.writer(response)
   writer.writerow(["nombre", "email", "fecha_registro"])
   for cliente in Cliente.objects.all():
       writer.writerow([cliente.nombre, cliente.email, cliente.fecha_registro])
   return response

Exportar CSV desde el panel de administración (con mixin reutilizable)

Desde Django Admin, también es posible implementar este tipo de funcionalidades:

class ExportCsvMixin:
    def export_as_csv(self, request, queryset):
        meta = self.model._meta
        field_names = [field.name for field in meta.fields]

        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = f'attachment; filename={meta}.csv'
        writer = csv.writer(response)

        writer.writerow(field_names)
        for obj in queryset:
            writer.writerow([getattr(obj, field) for field in field_names])

        return response

Agregarlo al admin:

@admin.register(Cliente)
class ClienteAdmin(admin.ModelAdmin, ExportCsvMixin):
   actions = ["export_as_csv"]

Cuando generes archivos desde Django, siempre recuerda:

writer = csv.writer(response, lineterminator="\n")

Preguntas frecuentes sobre CSV en Django

  • ¿Cómo depurar errores de encoding?
    • Prueba archivo.read().decode("utf-8", errors="ignore").
  • ¿Qué hacer si el CSV tiene demasiadas columnas?
    • Mapear solo las necesarias con un diccionario intermedio.
  • ¿Cómo procesar CSV grandes sin bloquear el servidor?
    • Usa tareas asíncronas (Celery o RQ).

Acepto recibir anuncios de interes sobre este Blog.

Veremos como escribir archivos CSV empleando Django y un módulo nativo de Python

| 👤 Andrés Cruz

🇺🇸 In english