diff --git a/README.md b/README.md
index 8337502..d5d3491 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ Demo:
## Requirements:
- python 3.10
-- django 4.1
+- django 5
- channels
- htmx > 1.8.5
@@ -36,10 +36,11 @@ python -m venv venv
source venv/bin/activate
```
-3. Install the required dependencies:
+3. Install the required dependencies, we use pip-tools for managing dependencies
```
-pip install -r requirements.txt
+pip install pip-tools
+pip-sync
```
4. Make Migrations:
@@ -54,3 +55,10 @@ python manage.py migrate
python manage.py runserver
```
+6. Create an admin user:
+
+```
+python manage.py createsuperuser
+```
+
+7. Visit the admin panel at http://localhost:8000/admin
diff --git a/accounts/admin.py b/accounts/admin.py
index 8c38f3f..8220d5c 100644
--- a/accounts/admin.py
+++ b/accounts/admin.py
@@ -1,3 +1,30 @@
from django.contrib import admin
-# Register your models here.
+from accounts.models import User
+
+
+@admin.register(User)
+class UserAdmin(admin.ModelAdmin):
+ list_display = ("username", "email", "is_superuser", "is_staff")
+ search_fields = (
+ "username",
+ "email",
+ )
+ list_filter = ("is_superuser", "is_staff")
+
+ fieldsets = (
+ (
+ None,
+ {
+ "fields": (
+ "username",
+ "email",
+ "is_superuser",
+ "is_staff",
+ "is_active",
+ "last_login",
+ "date_joined",
+ )
+ },
+ ),
+ )
diff --git a/accounts/forms.py b/accounts/forms.py
index 8840927..8647390 100644
--- a/accounts/forms.py
+++ b/accounts/forms.py
@@ -1,8 +1,8 @@
from accounts.models import User
from django.contrib.auth.forms import UserCreationForm
-class UserRegisterForm(UserCreationForm):
+class UserRegisterForm(UserCreationForm):
class Meta:
- model= User
- fields = ['username', 'password1', 'password2']
+ model = User
+ fields = ["username", "password1", "password2"]
diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py
index e1aacd0..a688e92 100644
--- a/accounts/migrations/0001_initial.py
+++ b/accounts/migrations/0001_initial.py
@@ -7,7 +7,6 @@
class Migration(migrations.Migration):
-
initial = True
dependencies = [
diff --git a/accounts/models.py b/accounts/models.py
index 4c9c171..3d30525 100644
--- a/accounts/models.py
+++ b/accounts/models.py
@@ -1,4 +1,5 @@
from django.contrib.auth.models import AbstractUser
+
class User(AbstractUser):
pass
diff --git a/accounts/views.py b/accounts/views.py
index e0338f8..32ccde8 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -14,10 +14,10 @@ def register(request):
user = authenticate(username=username, password=raw_password)
login(request, user)
messages.success(
- request, f"Your account has been created and you are now logged in as {username}"
+ request,
+ f"Your account has been created and you are now logged in as {username}",
)
return redirect("index")
else:
form = UserRegisterForm()
return render(request, "accounts/register.html", {"form": form})
-
diff --git a/backchat/asgi.py b/backchat/asgi.py
index 2b65ac2..628b989 100644
--- a/backchat/asgi.py
+++ b/backchat/asgi.py
@@ -3,16 +3,14 @@
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backchat.settings')
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backchat.settings")
from chat import routing
-application = ProtocolTypeRouter({
- 'http': get_asgi_application(),
- "websocket":AuthMiddlewareStack(
- URLRouter(
- routing.websocket_urlpatterns
- )
- )
-})
+application = ProtocolTypeRouter(
+ {
+ "http": get_asgi_application(),
+ "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
+ }
+)
diff --git a/backchat/settings.py b/backchat/settings.py
index e9dd718..45e4815 100644
--- a/backchat/settings.py
+++ b/backchat/settings.py
@@ -16,9 +16,7 @@
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
-
"channels",
-
"accounts",
"chat",
]
@@ -38,7 +36,9 @@
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
- "DIRS": ["templates",],
+ "DIRS": [
+ "templates",
+ ],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
@@ -94,7 +94,7 @@
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
CHANNEL_LAYERS = {
- 'default': {
+ "default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}
diff --git a/backchat/urls.py b/backchat/urls.py
index 6624991..8833b10 100644
--- a/backchat/urls.py
+++ b/backchat/urls.py
@@ -3,6 +3,6 @@
urlpatterns = [
path("admin/", admin.site.urls),
- path("auth/", include('accounts.urls')),
- path("", include('chat.urls')),
+ path("auth/", include("accounts.urls")),
+ path("", include("chat.urls")),
]
diff --git a/chat/admin.py b/chat/admin.py
index 8c38f3f..849b72d 100644
--- a/chat/admin.py
+++ b/chat/admin.py
@@ -1,3 +1,52 @@
from django.contrib import admin
-# Register your models here.
+from chat.models import Message
+from chat.models import Room
+
+
+@admin.register(Room)
+class RoomAdmin(admin.ModelAdmin):
+ list_display = (
+ "name",
+ "slug",
+ )
+ search_fields = (
+ "name",
+ "slug",
+ )
+ filter_horizontal = ("users",)
+
+
+@admin.register(Message)
+class MessageAdmin(admin.ModelAdmin):
+ readonly_fields = ["created_at"]
+
+ list_display = (
+ "room",
+ "user",
+ "message",
+
+ )
+ search_fields = (
+ "room",
+ "user",
+ "message",
+ )
+ list_filter = (
+ "room",
+ "user",
+ )
+
+ fieldsets = (
+ (
+ None,
+ {
+ "fields": (
+ "room",
+ "user",
+ "message",
+ "created_at",
+ )
+ },
+ ),
+ )
diff --git a/chat/consumers.py b/chat/consumers.py
index 9854277..491548e 100644
--- a/chat/consumers.py
+++ b/chat/consumers.py
@@ -12,19 +12,22 @@ async def connect(self):
self.room_group_name = "chat_%s" % self.room_name
self.user = self.scope["user"]
- await self.channel_layer.group_add(
- self.room_group_name, self.channel_name
- )
+ await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.add_user(self.room_name, self.user)
-
await self.accept()
+ messages = await self.get_room_messages(self.room_name)
+ for message in messages:
+ message_html = (
+ f"
"
+ f"
{message['username']}: {message['text']}
"
+ )
+ await self.send(text_data=json.dumps({"history": message_html}))
+
async def disconnect(self, close_code):
await self.remove_user(self.room_name, self.user)
- await self.channel_layer.group_discard(
- self.room_group_name, self.channel_name
- )
+ await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
@@ -33,35 +36,44 @@ async def receive(self, text_data):
username = user.username
room = self.room_name
- #await self.save_message(room, user, message)
+ await self.save_message(room, user, message)
await self.channel_layer.group_send(
- self.room_group_name,
+ self.room_group_name,
{
"type": "chat_message",
"message": message,
- #"room": room,
+ "room": room,
"username": username,
- }
+ },
)
async def chat_message(self, event):
message = event["message"]
- #room = event["room"]
+ room = event["room"]
username = event["username"]
-
- message_html = f""
+ message_html = (
+ f""
+ f"
{username}: {message}
"
+ )
await self.send(
text_data=json.dumps(
- {
- "message": message_html,
- #"room": room,
- "username": username
- }
+ {"message": message_html, "room": room, "username": username}
)
)
+ @sync_to_async
+ def get_room_messages(self, room_slug):
+ room = Room.objects.get(slug=room_slug)
+ messages = Message.objects.filter(room=room).order_by("-created_at")[
+ :50
+ ] # Adjust the number of messages as needed
+ return [
+ {"text": message.message, "username": message.user.username}
+ for message in messages
+ ]
+
@sync_to_async
def save_message(self, room, user, message):
room = Room.objects.get(slug=room)
diff --git a/chat/migrations/0001_initial.py b/chat/migrations/0001_initial.py
index c0a7d55..c4931c2 100644
--- a/chat/migrations/0001_initial.py
+++ b/chat/migrations/0001_initial.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
initial = True
dependencies = []
diff --git a/chat/migrations/0002_room_users_message.py b/chat/migrations/0002_room_users_message.py
index 01152de..7b31f53 100644
--- a/chat/migrations/0002_room_users_message.py
+++ b/chat/migrations/0002_room_users_message.py
@@ -6,7 +6,6 @@
class Migration(migrations.Migration):
-
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("chat", "0001_initial"),
diff --git a/chat/models.py b/chat/models.py
index da87adc..0fd40f7 100644
--- a/chat/models.py
+++ b/chat/models.py
@@ -1,6 +1,7 @@
from django.db import models
from accounts.models import User
+
class Room(models.Model):
name = models.CharField(max_length=128)
slug = models.SlugField(unique=True)
@@ -17,4 +18,4 @@ class Message(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
- return (self.room.name + " - " + str(self.user.username) + " : " + str(self.message))
+ return f"{self.room.name} - {str(self.user.username)} : {str(self.message)}"
diff --git a/chat/routing.py b/chat/routing.py
index 605437e..53db9a4 100644
--- a/chat/routing.py
+++ b/chat/routing.py
@@ -3,5 +3,5 @@
from chat import consumers
websocket_urlpatterns = [
- path('chat//', consumers.ChatConsumer.as_asgi()),
+ path("chat//", consumers.ChatConsumer.as_asgi()),
]
diff --git a/chat/urls.py b/chat/urls.py
index 18e60d1..93422ad 100644
--- a/chat/urls.py
+++ b/chat/urls.py
@@ -4,8 +4,9 @@
urlpatterns = [
- path("", TemplateView.as_view(template_name="base.html"), name='index'),
- path("chat/room//", views.index, name='chat'),
- path("create/", views.room_create, name='room-create'),
- path("join/", views.room_join, name='room-join'),
+ path("", TemplateView.as_view(template_name="base.html"), name="index"),
+
+ path("chat/room//", views.index, name="chat"),
+ path("create/", views.room_create, name="room-create"),
+ path("join/", views.room_join, name="room-join"),
]
diff --git a/chat/views.py b/chat/views.py
index 1e10c30..fe58cd9 100644
--- a/chat/views.py
+++ b/chat/views.py
@@ -1,32 +1,61 @@
-import string
import random
+import string
+
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, reverse, redirect
from django.utils.text import slugify
+
from chat.models import Room
@login_required
def index(request, slug):
- room = Room.objects.get(slug=slug)
- return render(request, 'chat/room.html', {'name': room.name, 'slug': room.slug})
+ try:
+ room = Room.objects.get(slug=slug)
+ except Room.DoesNotExist:
+ return render(
+ request,
+ "chat/join.html",
+ {"error": "This room does not exist. Please check the name and try again."},
+ )
+ return render(request, "chat/room.html", {"name": room.name, "slug": room.slug})
+
@login_required
def room_create(request):
- if request.method == "POST":
- room_name = request.POST["room_name"]
- uid = str(''.join(random.choices(string.ascii_letters + string.digits, k=4)))
- room_slug = slugify(room_name + "_" + uid)
- room = Room.objects.create(name=room_name, slug=room_slug)
- return redirect(reverse('chat', kwargs={'slug': room.slug}))
- else:
- return render(request, 'chat/create.html')
+ if request.method != "POST":
+ return render(request, "chat/create.html")
+
+ room_name = request.POST["room_name"]
+
+ try:
+ Room.objects.get(name=room_name)
+ return render(
+ request,
+ "chat/create.html",
+ {"error": "This room already exists. Please try again."},
+ )
+ except Room.DoesNotExist:
+ pass
+
+ uid = str("".join(random.choices(string.ascii_letters + string.digits, k=4)))
+ room_slug = slugify(f"{room_name}_{uid}")
+ room = Room.objects.create(name=room_name, slug=room_slug)
+ return redirect(reverse("chat", kwargs={"slug": room.slug}))
+
@login_required
def room_join(request):
- if request.method == "POST":
- room_name = request.POST["room_name"]
+ if request.method != "POST":
+ return render(request, "chat/join.html")
+
+ room_name = request.POST["room_name"]
+ try:
room = Room.objects.get(slug=room_name)
- return redirect(reverse('chat', kwargs={'slug': room.slug}))
- else:
- return render(request, 'chat/join.html')
+ except Room.DoesNotExist:
+ return render(
+ request,
+ "chat/join.html",
+ {"error": "This room does not exist. Please check the name and try again."},
+ )
+ return redirect(reverse("chat", kwargs={"slug": room.slug}))
diff --git a/requirements.in b/requirements.in
new file mode 100644
index 0000000..e09fc78
--- /dev/null
+++ b/requirements.in
@@ -0,0 +1,3 @@
+channels
+daphne
+django==5.0
diff --git a/requirements.txt b/requirements.txt
index 85cacfa..19c8739 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,24 +1,76 @@
-asgiref==3.6.0
-attrs==22.2.0
-autobahn==23.1.1
-Automat==22.10.0
-cffi==1.15.1
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# pip-compile
+#
+asgiref==3.7.2
+ # via
+ # channels
+ # daphne
+ # django
+attrs==23.2.0
+ # via
+ # automat
+ # service-identity
+ # twisted
+autobahn==23.6.2
+ # via daphne
+automat==22.10.0
+ # via twisted
+cffi==1.16.0
+ # via cryptography
channels==4.0.0
-constantly==15.1.0
-cryptography==39.0.0
+ # via -r requirements.in
+constantly==23.10.4
+ # via twisted
+cryptography==41.0.7
+ # via
+ # autobahn
+ # pyopenssl
+ # service-identity
daphne==4.0.0
-Django==4.1.6
+ # via -r requirements.in
+django==5.0
+ # via
+ # -r requirements.in
+ # channels
hyperlink==21.0.0
-idna==3.4
+ # via
+ # autobahn
+ # twisted
+idna==3.6
+ # via
+ # hyperlink
+ # twisted
incremental==22.10.0
-pyasn1==0.4.8
-pyasn1-modules==0.2.8
+ # via twisted
+pyasn1==0.5.1
+ # via
+ # pyasn1-modules
+ # service-identity
+pyasn1-modules==0.3.0
+ # via service-identity
pycparser==2.21
-pyOpenSSL==23.0.0
-service-identity==21.1.0
+ # via cffi
+pyopenssl==23.3.0
+ # via twisted
+service-identity==23.1.0
+ # via twisted
six==1.16.0
+ # via automat
sqlparse==0.4.3
-Twisted==22.10.0
+ # via django
+twisted[tls]==23.10.0
+ # via
+ # daphne
+ # twisted
txaio==23.1.1
-typing_extensions==4.4.0
-zope.interface==5.5.2
+ # via autobahn
+typing-extensions==4.9.0
+ # via twisted
+zope-interface==6.1
+ # via twisted
+
+# The following packages are considered to be unsafe in a requirements file:
+# setuptools
diff --git a/templates/chat/join.html b/templates/chat/join.html
index b847a06..886243b 100644
--- a/templates/chat/join.html
+++ b/templates/chat/join.html
@@ -6,4 +6,7 @@
+ {% if error %}
+ {{ error }}
+ {% endif %}
{% endblock %}