Skip to content

How I am using ChatGPT to increase my productivity coding Django

ChatGPT was released 30th November 2022 and since then it has taken the world by storm.

I have been using it to build some Django code and I thought it would be nice to show you the prompts I’m working with to help you too increase your Django productivity.

Before we begin, we need to understand that ChatGPT is pretty good – but not perfect. It can “hallucinate” facts – so take everything ChatGPT creates with a grain of salt. I like to consider ChatGPT as very good at producing “first drafts”. You ALWAYS have to review and edit what it creates.

ChatGPT as very good at producing “first drafts”.
You ALWAYS have to review and edit what ChatGPT creates.

For this example I’m using ChatGPT 3 which is the free version. ChatGPT 4 was released a few days ago but I haven’t been able to get my grubby little mitts on it yet.

So fire up ChatGPT (I just log in with a google account) and type this prompt:

Prompt: Hi ChatGPT – for the following conversation I would like you to assume I am using Python and the Django Framework.

ChatGPT: Sure, I’ll assume that you are using Python and the Django Framework. How can I assist you today?

This just makes it so we don’t have to type “in django” in our prompts. Ok lets ask it to create a model. For the purposes of this post, I’ve removed ChatGPT’s explanatory text – I’m just posting the relevant code.

Prompt: Please create a model for an address detailing unit, street (required), street2, suburb, postcode, state. Have the state be a foreign key to a state model and the suburb and postcode be a foreign key to a suburb model. Include __str__ and __repr__ on all models.

ChatGPT happily replies:

from django.db import models

class State(models.Model):
    name = models.CharField(max_length=50)
    abbreviation = models.CharField(max_length=3)

    def __str__(self):
        return self.name

class Suburb(models.Model):
    name = models.CharField(max_length=50)
    postcode = models.CharField(max_length=10)

    def __str__(self):
        return self.name

class Address(models.Model):
    unit = models.CharField(max_length=10, null=True, blank=True)
    street = models.CharField(max_length=100)
    street2 = models.CharField(max_length=100, null=True, blank=True)
    suburb = models.ForeignKey(Suburb, on_delete=models.CASCADE)
    state = models.ForeignKey(State, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.unit} {self.street}, {self.suburb.name}, {self.suburb.postcode}, {self.state.abbreviation}"

    def __repr__(self):
        return f"<Address(unit={self.unit}, street={self.street}, street2={self.street2}, suburb={self.suburb.name}, postcode={self.suburb.postcode}, state={self.state.name})>"

Ok this is pretty good. It created the base Address model, some fields (with sensible defaults on null/blank fields) and the foreign key to states.

It forgot the __repr__ on the state and Suburb models. Otherwise this is a very good start – and has certainly saved a lot of typing!

Now you still have to know Django! You cant let ChatGPT do it all for you. Personally I like my ID fields to all be uuids. So lets get ChatGPT to add them!

You still have to know Django! You can’t let ChatGPT do it all for you.

Prompt: With those models, please add a UUID as the primary key

import uuid
from django.db import models

class State(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=50)
    abbreviation = models.CharField(max_length=3)

    def __str__(self):
        return self.name

class Suburb(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=50)
    postcode = models.CharField(max_length=10)

    def __str__(self):
        return self.name

class Address(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    unit = models.CharField(max_length=10, null=True, blank=True)
    street = models.CharField(max_length=100)
    street2 = models.CharField(max_length=100, null=True, blank=True)
    suburb = models.ForeignKey(Suburb, on_delete=models.CASCADE)
    state = models.ForeignKey(State, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.unit} {self.street}, {self.suburb.name}, {self.suburb.postcode}, {self.state.abbreviation}"

    def __repr__(self):
        return f"<Address(id={self.id}, unit={self.unit}, street={self.street}, street2={self.street2}, suburb={self.suburb.name}, postcode={self.suburb.postcode}, state={self.state.name})>"

Ok now we have a pretty good model set. It even included the uuid library. Let’s get ChatGPT to create some admin pages for us.

Prompt: Please create an admin pages for those 3 models. Thank you.

from django.contrib import admin
from .models import State, Suburb, Address

class StateAdmin(admin.ModelAdmin):
    list_display = ('name', 'abbreviation')

class SuburbAdmin(admin.ModelAdmin):
    list_display = ('name', 'postcode')

class AddressAdmin(admin.ModelAdmin):
    list_display = ('unit', 'street', 'street2', 'suburb', 'state')

admin.site.register(State, StateAdmin)
admin.site.register(Suburb, SuburbAdmin)
admin.site.register(Address, AddressAdmin)

Ok now – we are getting somewhere – but this is just a half hearted attempt. Lets ask ChatGPT to work a little harder.

Prompt: Please create an admin pages for those 3 models – but be a little more verbose include search. Thank you.

from django.contrib import admin
from .models import State, Suburb, Address

class StateAdmin(admin.ModelAdmin):
    list_display = ('name', 'abbreviation')
    search_fields = ('name', 'abbreviation')

class SuburbAdmin(admin.ModelAdmin):
    list_display = ('name', 'postcode')
    search_fields = ('name', 'postcode')

class AddressAdmin(admin.ModelAdmin):
    list_display = ('unit', 'street', 'street2', 'suburb', 'state')
    search_fields = ('unit', 'street', 'street2', 'suburb__name', 'suburb__postcode', 'state__name', 'state__abbreviation')

admin.site.register(State, StateAdmin)
admin.site.register(Suburb, SuburbAdmin)
admin.site.register(Address, AddressAdmin)

Hmm not quite what we want. Oh no! I realised I forgot some date fields on those models.

Prompt: Please add the created_at, updated_at and deleted_at datefields to the three models. Regenerate the models and the admin please.

import uuid
from django.db import models

class State(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=100)
    abbreviation = models.CharField(max_length=2)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    deleted_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.name

    def __repr__(self):
        return f"<State(name={self.name}, abbreviation={self.abbreviation})>"

class Suburb(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=100)
    postcode = models.CharField(max_length=4)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    deleted_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return f"{self.name}, {self.postcode}"

    def __repr__(self):
        return f"<Suburb(name={self.name}, postcode={self.postcode})>"

class Address(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    unit = models.CharField(max_length=50, null=True, blank=True)
    street = models.CharField(max_length=100)
    street2 = models.CharField(max_length=100, null=True, blank=True)
    suburb = models.ForeignKey(Suburb, on_delete=models.CASCADE)
    postcode = models.CharField(max_length=4, null=True, blank=True)
    state = models.ForeignKey(State, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    deleted_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        if self.unit:
            return f"{self.unit} {self.street}, {self.suburb}, {self.state}"
        else:
            return f"{self.street}, {self.suburb}, {self.state}"

    def __repr__(self):
        return f"<Address(unit={self.unit}, street={self.street}, suburb={self.suburb.name}, postcode={self.suburb.postcode}, state={self.state.name}, created_at={self.created_at}, updated_at={self.updated_at}, deleted_at={self.deleted_at})>"
from django.contrib import admin
from .models import State, Suburb, Address

class StateAdmin(admin.ModelAdmin):
    list_display = ('name', 'abbreviation', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('name', 'abbreviation', 'created_at', 'updated_at', 'deleted_at')

class SuburbAdmin(admin.ModelAdmin):
    list_display = ('name', 'postcode', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('name', 'postcode', 'created_at', 'updated_at', 'deleted_at')

class AddressAdmin(admin.ModelAdmin):
    list_display = ('unit', 'street', 'street2', 'suburb', 'postcode', 'state', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('unit', 'street', 'street2', 'suburb__name', 'suburb__

Ok nice! But I’m not happy with the admin yet.

Prompt: Please regenerate the admin file to include fieldsets and list filtering

from django.contrib import admin
from .models import State, Suburb, Address

class StateAdmin(admin.ModelAdmin):
    list_display = ('name', 'abbreviation', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('name', 'abbreviation', 'created_at', 'updated_at', 'deleted_at')
    list_filter = ('created_at', 'updated_at', 'deleted_at')
    fieldsets = (
        (None, {'fields': ('name', 'abbreviation')}),
        ('Audit', {'fields': ('created_at', 'updated_at', 'deleted_at')}),
    )

class SuburbAdmin(admin.ModelAdmin):
    list_display = ('name', 'postcode', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('name', 'postcode', 'created_at', 'updated_at', 'deleted_at')
    list_filter = ('created_at', 'updated_at', 'deleted_at')
    fieldsets = (
        (None, {'fields': ('name', 'postcode')}),
        ('Audit', {'fields': ('created_at', 'updated_at', 'deleted_at')}),
    )

class AddressAdmin(admin.ModelAdmin):
    list_display = ('unit', 'street', 'street2', 'suburb', 'postcode', 'state', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('unit', 'street', 'street2', 'suburb__name', 'suburb__postcode', 'state__name', 'created_at', 'updated_at', 'deleted_at')
    list_filter = ('created_at', 'updated_at', 'deleted_at', 'state', 'suburb__postcode')
    fieldsets = (
        (None, {'fields': ('unit', 'street', 'street2', 'suburb', 'postcode', 'state')}),
        ('Audit', {'fields': ('created_at', 'updated_at', 'deleted_at')}),
    )

admin.site.register(State, StateAdmin)
admin.site.register(Suburb, SuburbAdmin)
admin.site.register(Address, AddressAdmin)

Ok but one more adjustment!

Prompt: Please adjust the admin page to include the register decorator rather than the admin.site.register() calls.

from django.contrib import admin
from .models import State, Suburb, Address

@admin.register(State)
class StateAdmin(admin.ModelAdmin):
    list_display = ('name', 'abbreviation', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('name', 'abbreviation', 'created_at', 'updated_at', 'deleted_at')
    list_filter = ('created_at', 'updated_at', 'deleted_at')
    fieldsets = (
        (None, {'fields': ('name', 'abbreviation')}),
        ('Audit', {'fields': ('created_at', 'updated_at', 'deleted_at')}),
    )

@admin.register(Suburb)
class SuburbAdmin(admin.ModelAdmin):
    list_display = ('name', 'postcode', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('name', 'postcode', 'created_at', 'updated_at', 'deleted_at')
    list_filter = ('created_at', 'updated_at', 'deleted_at')
    fieldsets = (
        (None, {'fields': ('name', 'postcode')}),
        ('Audit', {'fields': ('created_at', 'updated_at', 'deleted_at')}),
    )

@admin.register(Address)
class AddressAdmin(admin.ModelAdmin):
    list_display = ('unit', 'street', 'street2', 'suburb', 'postcode', 'state', 'created_at', 'updated_at', 'deleted_at')
    search_fields = ('unit', 'street', 'street2', 'suburb__name', 'suburb__postcode', 'state__name', 'created_at', 'updated_at', 'deleted_at')
    list_filter = ('created_at', 'updated_at', 'deleted_at', 'state', 'suburb__postcode')
    fieldsets = (
        (None, {'fields': ('unit', 'street', 'street2', 'suburb', 'postcode', 'state')}),
        ('Audit', {'fields': ('created_at', 'updated_at', 'deleted_at')}),
    )

Ok I’m happy enough with that for now. As I use the admin, I might adjust the fields but I would do that manually.

Let’s implement our beautiful model to use something like the amazing Django Rest Framework.

Prompt: Please create a serializer for our 3 models to be used by django rest framework, listing each field explicitly.

from rest_framework import serializers
from .models import State, Suburb, Address


class StateSerializer(serializers.ModelSerializer):
    name = serializers.CharField()
    abbreviation = serializers.CharField()

    class Meta:
        model = State
        fields = ('id', 'name', 'abbreviation', 'created_at', 'updated_at', 'deleted_at')


class SuburbSerializer(serializers.ModelSerializer):
    name = serializers.CharField()
    postcode = serializers.CharField()

    class Meta:
        model = Suburb
        fields = ('id', 'name', 'postcode', 'created_at', 'updated_at', 'deleted_at')


class AddressSerializer(serializers.ModelSerializer):
    unit = serializers.CharField(allow_null=True, allow_blank=True)
    street = serializers.CharField()
    street2 = serializers.CharField(allow_null=True, allow_blank=True)
    suburb = SuburbSerializer()
    postcode = serializers.CharField()
    state = StateSerializer()
    created_at = serializers.DateTimeField(read_only=True)
    updated_at = serializers.DateTimeField(read_only=True)
    deleted_at = serializers.DateTimeField(read_only=True)

    class Meta:
        model = Address
        fields = ('id', 'unit', 'street', 'street2', 'suburb', 'postcode', 'state', 'created_at', 'updated_at', 'deleted_at')

Oh my – that is a damn good start! I can work with that and edit as needed. I’m a function based views kinda guy – and it’s a hill I’ll die on!

Prompt: Create the views that use those serializers for the django rest framework. Use function based views please.

from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import State, Suburb, Address
from .serializers import StateSerializer, SuburbSerializer, AddressSerializer


@api_view(['GET', 'POST'])
def state_list(request):
    if request.method == 'GET':
        states = State.objects.all()
        serializer = StateSerializer(states, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = StateSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)


@api_view(['GET', 'PUT', 'DELETE'])
def state_detail(request, pk):
    try:
        state = State.objects.get(pk=pk)
    except State.DoesNotExist:
        return Response(status=404)

    if request.method == 'GET':
        serializer = StateSerializer(state)
        return Response(serializer.data)
    elif request.method == 'PUT':
        serializer = StateSerializer(state, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=400)
    elif request.method == 'DELETE':
        state.delete()
        return Response(status=204)


@api_view(['GET', 'POST'])
def suburb_list(request):
    if request.method == 'GET':
        suburbs = Suburb.objects.all()
        serializer = SuburbSerializer(suburbs, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = SuburbSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)


@api_view(['GET', 'PUT', 'DELETE'])
def suburb_detail(request, pk):
    try:
        suburb = Suburb.objects.get(pk=pk)
    except Suburb.DoesNotExist:
        return Response(status=404)

    if request.method == 'GET':
        serializer = SuburbSerializer(suburb)
        return Response(serializer.data)
    elif request.method == 'PUT':
        serializer = SuburbSerializer(suburb, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=400)
    elif request.method == 'DELETE':
        suburb.delete()
        return Response(status=204)


@api_view(['GET', 'POST'])
def address_list(request):
    if request.method == 'GET':
        addresses = Address.objects.all()
        serializer = AddressSerializer(addresses, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = AddressSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)


@api_view(['GET', 'PUT', 'DELETE'])
def address_detail(request, pk):
    try:
        address = Address.objects.get(pk=pk)
    except Address.DoesNotExist:
        return Response(status=404)

    if request.method == 'GET':
        serializer = AddressSerializer(address)
        return Response(serializer.data)
    elif request.method == 'PUT':
        serializer = AddressSerializer(address, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=400)
    elif request.method == 'DELETE':
        address.delete()
        return Response(status=204)

Ok – are we saving time yet? Yes, you still need to know Django – but all the structural/boilerplate code is written for you – and you can edit what is produced.

We are almost done – let’s ask it to help use use our nice models and serializers:

Prompt: Please write curl commands to do a POST request to each model.

curl -X POST -H "Content-Type: application/json" -d '{"unit": "10A", "street": "Main St", "street2": "Apt 3", "suburb": "Newtown", "postcode": "2042", "state": "NSW"}' http://localhost:8000/api/addresses/

curl -X POST -H "Content-Type: application/json" -d '{"name": "Victoria", "code": "VIC"}' http://localhost:8000/api/states/

curl -X POST -H "Content-Type: application/json" -d '{"name": "Richmond", "postcode": "3121"}' http://localhost:8000/api/suburbs/

Well that is certainly useful – and stops me from needing to check the curl docs!

And “What about TESTS?” I hear the Greybeard on your shoulder scream. Well – we can begin our test suite thusly:

Prompt: Please write some tests for the code we have produced today.

from django.test import TestCase, Client
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase, APIClient
from .models import Address, State, Suburb
from .serializers import AddressSerializer, StateSerializer, SuburbSerializer


# Test Models
class AddressModelTestCase(TestCase):

    def setUp(self):
        self.address = Address.objects.create(
            unit="10A",
            street="Main St",
            street2="Apt 3",
            suburb=Suburb.objects.create(name="Newtown", postcode="2042"),
            state=State.objects.create(name="New South Wales", code="NSW")
        )

    def test_address_model(self):
        self.assertEqual(str(self.address), "10A, Main St, Apt 3, Newtown, 2042, New South Wales")


class StateModelTestCase(TestCase):

    def setUp(self):
        self.state = State.objects.create(name="Victoria", code="VIC")

    def test_state_model(self):
        self.assertEqual(str(self.state), "Victoria")


class SuburbModelTestCase(TestCase):

    def setUp(self):
        self.suburb = Suburb.objects.create(name="Richmond", postcode="3121")

    def test_suburb_model(self):
        self.assertEqual(str(self.suburb), "Richmond, 3121")


# Test Serializers
class AddressSerializerTestCase(TestCase):

    def setUp(self):
        self.address_data = {
            "unit": "10A",
            "street": "Main St",
            "street2": "Apt 3",
            "suburb": {"name": "Newtown", "postcode": "2042"},
            "state": {"name": "New South Wales", "code": "NSW"}
        }
        self.serializer = AddressSerializer(data=self.address_data)

    def test_address_serializer_valid(self):
        self.assertTrue(self.serializer.is_valid())

    def test_address_serializer_data(self):
        self.assertEqual(set(self.serializer.data.keys()), set(["unit", "street", "street2", "suburb", "postcode", "state"]))


class StateSerializerTestCase(TestCase):

    def setUp(self):
        self.state_data = {"name": "Victoria", "code": "VIC"}
        self.serializer = StateSerializer(data=self.state_data)

    def test_state_serializer_valid(self):
        self.assertTrue(self.serializer.is_valid())

    def test_state_serializer_data(self):
        self.assertEqual(set(self.serializer.data.keys()), set(["name", "code"]))


class SuburbSerializerTestCase(TestCase):

    def setUp(self):
        self.suburb_data = {"name": "Richmond", "postcode": "3121"}
        self.serializer = SuburbSerializer(data=self.suburb_data)

    def test_suburb_serializer_valid(self):
        self.assertTrue(self.serializer.is_valid())

    def test_suburb_serializer_data(self):
        self.assertEqual(set(self.serializer.data.keys()), set(["name", "postcode"]))


# Test Views
class AddressViewSetTestCase(APITestCase):

    def setUp(self):
        self.client = APIClient()
        self.address = Address.objects.create(
            unit="10A",
            street="Main St",
            street2="Apt 3",
            suburb=Suburb.objects.create(name="Newtown", postcode="2042"),
            state=State.objects.create(name="New South Wales", code="NSW")
        )
        self.valid_payload = {
            "unit": "10B",
            "street": "Oxford St",
            "street2": "",
            "suburb": {"name": "Darlinghurst", "postcode": "2010"},
            "state": {"name": "New South Wales", "code":

Oh my goodness! That is a great start to our test suite! Let’s use that and build other tests from there.

And finally – let’s thank ChatGPT for being so helpful. When the robots take over the world, I want them to know I was always polite and hopefully they will spare me.

Prompt: Thank you so much ChatGPT – you have saved me a lot of time!

ChatGPT: You’re welcome! I’m glad I could be of help. If you have any more questions or need further assistance, feel free to ask!

So there you have it – ChatGPT is not perfect but it certainly produces a lot of code for you that is pretty good. It reduces you need to type the standard code stuff – and give you a good basis to develop your application.

ChatGPT is continually getting better – and ChatGPT 4 (I’ve heard) is much, much better. Let’s see how these AI tools can help us become more productive in the future!

Leave a Reply