Write CSV with Django

Video thumbnail

Working with CSV files in Django can be as simple as using a csv.reader... or as frustrating as receiving a completely broken Excel file from a client. Over time, I've had to export client data directly from the database and also had to deal with that awful Excel file we've all suffered from at some point. So, in this article, I'm going to explain everything you need to know to handle CSV in Django without losing your patience.

What is a CSV file and why does Django handle it so well

CSV (Comma Separated Values) is one of the simplest and most useful formats for representing tabular data. Django interacts wonderfully with it because:

  • It is plain text
  • It works perfectly with Python and the csv module
  • It is lightweight, fast, and requires no external dependencies

Advantages of the CSV format for databases

  • Easy to generate from Django.
  • Perfect for bulk record import.
  • Compatible with any system: Excel, Google Sheets, databases, ERPs.

Preparing your files before working with CSV in Django

How to correctly convert an Excel file to CSV

The conversion seems simple: open in Excel → Save as CSV.
But in practice, you must consider:

  • UTF-8 encoding
  • Correct separator (comma or semicolon)
  • Cleaning of empty rows
  • Making sure data is ordered and ready for processing.
  • Handling large files
    • Use StreamingHttpResponse if the file exceeds thousands of records.
  • Avoiding duplicates and ensuring integrity
    • get_or_create() is your best ally.
  • Recommended encoding to prevent errors
    • UTF-8 without BOM → the safe option.

Validating headers, separators, encoding, and newline

Before importing a CSV into Django, check:

  • That columns have clear names without strange spaces
  • That separators are homogeneous
  • That the file uses the correct newline (`newline=""`)
  • That there are no broken tildes or phantom characters

Typical errors when receiving files from clients

When I received that impossible Excel file, I discovered errors like:

  • Mixed columns
  • Repeated headers
  • Dates with various formats
  • Rows with "invisible" values

How to import CSV into Django models step by step

 Structuring your model for a clean import

A typical model might look like this:

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

Using csv.reader and DictReader

  • csv.reader: works with lists
  • csv.DictReader: works with dictionaries using CSV headers

Saving records with objects.create() and get_or_create()

Example:

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"]
           }
       )

Data validation: dates, decimals, and empty fields

When I processed real client data, I encountered dates like 12/1/23, 2023-01-12, and even "enero 2023" (January 2023).
My rule: normalize before saving.

Importing CSV from a Django view (practical example)

Uploading a CSV file from a form

A straightforward way:

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

Processing rows and handling errors

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")

How to export data to CSV in Django

For example, I had to export client data to send personalized reports. And right after that, I had to take a completely messy Excel file from the client, convert it to CSV, and process it from Django. CSV is that tool that always gets you out of trouble.

To write files, the same scheme as the previous codes is used, but the files must be opened in write mode and the writer() function must be used instead of the reader() function; it is also important to note that the file to be written does not have to exist (in the following example, to avoid deleting the previous file, the file to be referenced is called "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')

When in write mode, a structure must be presented that we want to replicate in the file; in this case, it would be lists, which, as we saw in the reading examples, are the formats returned when reading the files.

To write a list, the writerow() function is used; you can also use writerows() to write multiple lists:

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

writer.writerows(data)

The newline option is also used as an empty string; this prevents a space from being placed between records when a new column is written; without defining 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),

And we will have an output file with the format we specified earlier:

documents\Libro2.csv

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

Another example:

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

Exporting CSV from the admin panel (with a reusable mixin)

It is also possible to implement this type of functionality from Django Admin:

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

Adding it to the admin:

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

When generating files from Django, always remember:

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

Frequently Asked Questions about CSV in Django

  • How to debug encoding errors?
    • Try `archivo.read().decode("utf-8", errors="ignore")`.
  • What to do if the CSV has too many columns?
    • Map only the necessary ones with an intermediate dictionary.
  • How to process large CSV files without blocking the server?
    • Use asynchronous tasks (Celery or RQ).

I agree to receive announcements of interest about this Blog.

We will see how to write and read CSV files using Django and a native Python module, several examples and recommendations for its use.

| 👤 Andrés Cruz

🇪🇸 En español