Create Chat App

Table of Contents

Django ChatGPT Tutorial Series:

  1. Introduction
  2. Create Django Project with Modern Frontend Tooling
  3. Create Chat App
  4. Partial Form Submission With Turbo Frame
  5. Use Turbo Stream To Manipulate DOM Elements
  6. Send Turbo Stream Over Websocket
  7. Using OpenAI Streaming API With Celery
  8. Use Stimulus to Better Organize Javascript Code in Django
  9. Use Stimulus to Render Markdown and Highlight Code Block
  10. Use Stimulus to Improve UX of Message Form
  11. Source Code chatgpt-django-project

In this article, we will create a chat app, and relevant models, views and templates.

Chat App

Let's first create django app chat

# we put all django apps under the chatgpt_django_app
(venv)$ mkdir -p ./chatgpt_django_app/chat
(venv)$ python manage.py startapp chat ./chatgpt_django_app/chat

We will have structure like this

├── chatgpt_django_app
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── chat                      # new
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── migrations
│   │   ├── models.py
│   │   ├── tests.py
│   │   └── views.py
│   ├── templates
│   │   └── index.html
│   ├── urls.py
│   └── wsgi.py

Update chatgpt_django_app/chat/apps.py to change the name to chatgpt_django_app.chat

from django.apps import AppConfig


class ChatConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'chatgpt_django_app.chat'                 # update

Add chatgpt_django_app.chat to the INSTALLED_APPS in chatgpt_django_app/settings.py

INSTALLED_APPS = [
    ...
    'chatgpt_django_app.chat',                  # new
]
# check if there is any error
(venv)$ ./manage.py check

System check identified no issues (0 silenced).

Model

Update chatgpt_django_app/chat/models.py

from django.db import models


class Chat(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]


class Message(models.Model):
    SYSTEM = 0
    ASSISTANT = 10
    USER = 20
    ROLE_CHOICES = (
        (SYSTEM, "System"),
        (ASSISTANT, "Assistant"),
        (USER, "User"),
    )

    role = models.IntegerField(choices=ROLE_CHOICES)
    chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name="messages")
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["created_at"]

    @property
    def role_label(self):
        role_label = self.get_role_display()
        return role_label

    @classmethod
    def for_openai(cls, messages):
        return [
            {"role": message.role_label.lower(), "content": message.content}
            for message in messages
        ]

Notes:

  1. Chat model is used to group messages
  2. role field of the message is used to distinguish the message is from system, assistant or user.

Migrate the db

(venv)$ python manage.py makemigrations
(venv)$ python manage.py migrate

Form

Create chatgpt_django_app/chat/forms.py

from django import forms

from .models import Chat, Message


class MessageForm(forms.ModelForm):

    class Meta:
        model = Message
        fields = ("content",)

    def __init__(self, *args, **kwargs):
        self.role = kwargs.pop("role")
        self.chat_pk = kwargs.pop("chat_pk")

        super().__init__(*args, **kwargs)

    def save(self, commit=True):
        instance = super().save(commit=False)
        instance.chat_id = self.chat_pk
        instance.role = self.role
        if commit:
            instance.save()
        return instance

We will use this form to create message in a bit.

View

Next, we will create views for the chat app.

Edit chatgpt_django_app/chat/views.py

from django.urls import reverse
from django.views import View
from django.views.generic.list import ListView
from django.http import HttpResponseRedirect

from .models import Chat, Message


class IndexView(View):

    def get(self, request):
        # If no chat exists, create a new chat and redirect to the message list page.
        chat = Chat.objects.first()
        if not chat:
            chat = Chat.objects.create()
        return HttpResponseRedirect(reverse("chat:message-list", args=[chat.pk]))

    def post(self, request, *args, **kwargs):
        # create new chat object and redirect to message list view
        instance = Chat.objects.create()
        return HttpResponseRedirect(reverse("chat:message-list", args=[instance.pk]))


index_view = IndexView.as_view()


class MessageListView(ListView):
    model = Message
    template_name = "message_list_page.html"

    def get_queryset(self):
        qs = super().get_queryset()
        qs = qs.filter(chat_id=self.kwargs["chat_pk"])
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["chats"] = Chat.objects.all()
        return context


message_list_view = MessageListView.as_view()

Notes:

  1. When we visit the index page, we will create a new chat object if there is no chat object exists.
  2. If we send a POST request to the index page, we will create a new chat object and redirect to the message list page.
  3. MessageListView is to display all message for a chat. It will also display all chats in the sidebar, so user can switch to another chat.

Templates

Create chatgpt_django_app/templates/base.html

{% load webpack_loader static %}

<!DOCTYPE html>
<html>
<head>
  <title>ChatGPT Demo</title>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  {% stylesheet_pack 'app' %}
  {% javascript_pack 'app' attrs='defer' %}

</head>
<body>

{% block content %}
{% endblock %}

</body>
</html>

Notes:

  1. We move JS from the end of the body to the head element, and set defer attribute.
  2. If the defer attribute is set, it specifies that the script is downloaded in parallel to parsing the page, and executed after the page has finished parsing

Create chatgpt_django_app/templates/message_list_page.html

{% extends "base.html" %}

{% block content %}

<main class="w-full">
  <div class="grid grid-cols-12 gap-2">
    <div class="col-span-12 bg-gray-50 sm:col-span-3">
      <div class="flex flex-col space-y-1 sm:h-screen">

        <form method="post" action="{% url 'chat:index' %}">
          {% csrf_token %}
          <button type="submit" class="bg-green-500 text-white py-2 px-4 rounded m-2">Start a new conversation</button>
        </form>

        <ul class="flex flex-col py-4 space-y-2">
          {% for chat in chats %}
          <a class="text-white py-2 px-2 rounded mx-2 bg-blue-500"
             href="{% url 'chat:message-list' chat.pk %}">Chat: created on <time>{{ chat.created_at }}</time></a>
          {% endfor %}
        </ul>

      </div>
    </div>
    <div class="col-span-12 bg-gray-50 sm:col-span-9">
      <div class="bg-gray-50 sm:h-screen">

        <div class="flex flex-col h-full">

          <!-- Message List -->
          <div class="overflow-y-auto flex-1">
            {% for instance in object_list %}
            <div id="message_{{ instance.pk }}">
              {% if instance.role_label == 'User' %}
              <div class="p-4 m-4 max-w-full text-black rounded-lg bg-sky-100 prose" >{{ instance.content }}</div>
              {% else %}
              <div class="p-4 m-4 max-w-full bg-gray-200 rounded-lg prose">{{ instance.content }}</div>
              {% endif %}
            </div>
            {% endfor %}
          </div>

          <!-- Message Form -->
          <div>
          </div>

        </div>

      </div>
    </div>
  </div>
</main>

{% endblock %}

Urls

Create chatgpt_django_app/chat/urls.py

from django.urls import path

from .views import (
    index_view,
    message_list_view,
)

app_name = "chat"

urlpatterns = [
    path("chat/", index_view, name="index"),
    path("chat/<int:chat_pk>/message/list/", message_list_view, name="message-list"),
]

Update chatgpt_django_app/urls.py

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

urlpatterns = [
    path('', include("chatgpt_django_app.chat.urls")),        # new
    path('admin/', admin.site.urls),
]

Test

Let's run the app

$ npm run start

And then run Django server

(venv)$ python manage.py runserver

Open http://localhost:8000/chat/ in your browser, we should see the message list page.

Django ChatGPT Tutorial Series:

  1. Introduction
  2. Create Django Project with Modern Frontend Tooling
  3. Create Chat App
  4. Partial Form Submission With Turbo Frame
  5. Use Turbo Stream To Manipulate DOM Elements
  6. Send Turbo Stream Over Websocket
  7. Using OpenAI Streaming API With Celery
  8. Use Stimulus to Better Organize Javascript Code in Django
  9. Use Stimulus to Render Markdown and Highlight Code Block
  10. Use Stimulus to Improve UX of Message Form
  11. Source Code chatgpt-django-project
Launch Products Faster with Django

Unlock the power of Django combined with Hotwire through SaaS Hammer. Supercharge productivity, tap into Python's rich ecosystem, and focus on perfecting your product!

Michael Yin

Michael Yin

Michael is a Full Stack Developer who loves writing code, tutorials about Django, and modern frontend techs.

He has published some tech course on testdriven.io and ebooks on leanpub.

© 2024 SaaS Hammer