diff options
Diffstat (limited to 'chat')
| -rw-r--r-- | chat/__init__.py | 0 | ||||
| -rw-r--r-- | chat/asgi.py | 28 | ||||
| -rw-r--r-- | chat/consumers.py | 65 | ||||
| -rw-r--r-- | chat/routing.py | 7 | ||||
| -rw-r--r-- | chat/settings.py | 135 | ||||
| -rw-r--r-- | chat/templates/chat.html | 136 | ||||
| -rw-r--r-- | chat/templates/room.html | 451 | ||||
| -rw-r--r-- | chat/urls.py | 26 | ||||
| -rw-r--r-- | chat/views.py | 14 | ||||
| -rw-r--r-- | chat/wsgi.py | 16 |
10 files changed, 878 insertions, 0 deletions
diff --git a/chat/__init__.py b/chat/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/chat/__init__.py | |||
diff --git a/chat/asgi.py b/chat/asgi.py new file mode 100644 index 0000000..247393e --- /dev/null +++ b/chat/asgi.py | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | """ | ||
| 2 | ASGI config for chat project. | ||
| 3 | |||
| 4 | It exposes the ASGI callable as a module-level variable named ``application``. | ||
| 5 | |||
| 6 | For more information on this file, see | ||
| 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ | ||
| 8 | """ | ||
| 9 | |||
| 10 | import os | ||
| 11 | |||
| 12 | from channels.auth import AuthMiddlewareStack | ||
| 13 | from channels.routing import ProtocolTypeRouter, URLRouter | ||
| 14 | from channels.security.websocket import AllowedHostsOriginValidator | ||
| 15 | from django.core.asgi import get_asgi_application | ||
| 16 | |||
| 17 | from chat.routing import websocket_urlpatterns | ||
| 18 | |||
| 19 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat.settings') | ||
| 20 | |||
| 21 | application = ProtocolTypeRouter( | ||
| 22 | { | ||
| 23 | "http": get_asgi_application(), | ||
| 24 | "websocket": AllowedHostsOriginValidator( | ||
| 25 | AuthMiddlewareStack(URLRouter(websocket_urlpatterns)) | ||
| 26 | ), | ||
| 27 | } | ||
| 28 | ) | ||
diff --git a/chat/consumers.py b/chat/consumers.py new file mode 100644 index 0000000..bfba381 --- /dev/null +++ b/chat/consumers.py | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | import json | ||
| 2 | |||
| 3 | from asgiref.sync import async_to_sync | ||
| 4 | from channels.generic.websocket import WebsocketConsumer | ||
| 5 | |||
| 6 | |||
| 7 | class ChatConsumer(WebsocketConsumer): | ||
| 8 | def connect(self): | ||
| 9 | self.room_name = self.scope["url_route"]["kwargs"]["room_name"] | ||
| 10 | self.room_group_name = f"chat_{self.room_name}" | ||
| 11 | self.username = "Anonymous" | ||
| 12 | |||
| 13 | async_to_sync(self.channel_layer.group_add)( | ||
| 14 | self.room_group_name, self.channel_name | ||
| 15 | ) | ||
| 16 | self.accept() | ||
| 17 | |||
| 18 | def disconnect(self, close_code): | ||
| 19 | async_to_sync(self.channel_layer.group_discard)( | ||
| 20 | self.room_group_name, self.channel_name | ||
| 21 | ) | ||
| 22 | async_to_sync(self.channel_layer.group_send)( | ||
| 23 | self.room_group_name, | ||
| 24 | {"type": "system.message", "text": f"{self.username} left the chat"}, | ||
| 25 | ) | ||
| 26 | |||
| 27 | def receive(self, text_data): | ||
| 28 | text_data_json = json.loads(text_data) | ||
| 29 | msg_type = text_data_json.get("type", "message") | ||
| 30 | |||
| 31 | if msg_type == "join": | ||
| 32 | self.username = text_data_json.get("username", "Anonymous") | ||
| 33 | async_to_sync(self.channel_layer.group_send)( | ||
| 34 | self.room_group_name, | ||
| 35 | {"type": "system.message", "text": f"{self.username} joined the chat"}, | ||
| 36 | ) | ||
| 37 | return | ||
| 38 | |||
| 39 | if msg_type == "rename": | ||
| 40 | old_name = self.username | ||
| 41 | self.username = text_data_json.get("username", self.username) | ||
| 42 | async_to_sync(self.channel_layer.group_send)( | ||
| 43 | self.room_group_name, | ||
| 44 | {"type": "system.message", "text": f"{old_name} is now known as {self.username}"}, | ||
| 45 | ) | ||
| 46 | return | ||
| 47 | |||
| 48 | message = text_data_json["message"] | ||
| 49 | username = text_data_json.get("username", self.username) | ||
| 50 | async_to_sync(self.channel_layer.group_send)( | ||
| 51 | self.room_group_name, | ||
| 52 | {"type": "chat.message", "message": message, "username": username}, | ||
| 53 | ) | ||
| 54 | |||
| 55 | def chat_message(self, event): | ||
| 56 | self.send(text_data=json.dumps({ | ||
| 57 | "message": event["message"], | ||
| 58 | "username": event["username"], | ||
| 59 | })) | ||
| 60 | |||
| 61 | def system_message(self, event): | ||
| 62 | self.send(text_data=json.dumps({ | ||
| 63 | "type": "system", | ||
| 64 | "text": event["text"], | ||
| 65 | })) | ||
diff --git a/chat/routing.py b/chat/routing.py new file mode 100644 index 0000000..9f08ddf --- /dev/null +++ b/chat/routing.py | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | from django.urls import re_path | ||
| 2 | |||
| 3 | from . import consumers | ||
| 4 | |||
| 5 | websocket_urlpatterns = [ | ||
| 6 | re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()), | ||
| 7 | ] | ||
diff --git a/chat/settings.py b/chat/settings.py new file mode 100644 index 0000000..653eea9 --- /dev/null +++ b/chat/settings.py | |||
| @@ -0,0 +1,135 @@ | |||
| 1 | """ | ||
| 2 | Django settings for chat project. | ||
| 3 | |||
| 4 | Generated by 'django-admin startproject' using Django 5.0.3. | ||
| 5 | |||
| 6 | For more information on this file, see | ||
| 7 | https://docs.djangoproject.com/en/5.0/topics/settings/ | ||
| 8 | |||
| 9 | For the full list of settings and their values, see | ||
| 10 | https://docs.djangoproject.com/en/5.0/ref/settings/ | ||
| 11 | """ | ||
| 12 | |||
| 13 | from pathlib import Path | ||
| 14 | |||
| 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. | ||
| 16 | BASE_DIR = Path(__file__).resolve().parent.parent | ||
| 17 | |||
| 18 | |||
| 19 | # Quick-start development settings - unsuitable for production | ||
| 20 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ | ||
| 21 | |||
| 22 | # SECURITY WARNING: keep the secret key used in production secret! | ||
| 23 | SECRET_KEY = 'django-insecure-dxn^kf6b8qm@#cqg4+jncop793g7*3mgok&p$40di_cxbm+y$3' | ||
| 24 | |||
| 25 | # SECURITY WARNING: don't run with debug turned on in production! | ||
| 26 | DEBUG = True | ||
| 27 | |||
| 28 | ALLOWED_HOSTS = ['*'] | ||
| 29 | |||
| 30 | |||
| 31 | # Application definition | ||
| 32 | |||
| 33 | INSTALLED_APPS = [ | ||
| 34 | 'chat', | ||
| 35 | 'daphne', | ||
| 36 | 'django.contrib.admin', | ||
| 37 | 'django.contrib.auth', | ||
| 38 | 'django.contrib.contenttypes', | ||
| 39 | 'django.contrib.sessions', | ||
| 40 | 'django.contrib.messages', | ||
| 41 | 'django.contrib.staticfiles', | ||
| 42 | ] | ||
| 43 | |||
| 44 | MIDDLEWARE = [ | ||
| 45 | 'django.middleware.security.SecurityMiddleware', | ||
| 46 | 'django.contrib.sessions.middleware.SessionMiddleware', | ||
| 47 | 'django.middleware.common.CommonMiddleware', | ||
| 48 | 'django.middleware.csrf.CsrfViewMiddleware', | ||
| 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', | ||
| 50 | 'django.contrib.messages.middleware.MessageMiddleware', | ||
| 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', | ||
| 52 | ] | ||
| 53 | |||
| 54 | ROOT_URLCONF = 'chat.urls' | ||
| 55 | |||
| 56 | TEMPLATES = [ | ||
| 57 | { | ||
| 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', | ||
| 59 | 'DIRS': [], | ||
| 60 | 'APP_DIRS': True, | ||
| 61 | 'OPTIONS': { | ||
| 62 | 'context_processors': [ | ||
| 63 | 'django.template.context_processors.debug', | ||
| 64 | 'django.template.context_processors.request', | ||
| 65 | 'django.contrib.auth.context_processors.auth', | ||
| 66 | 'django.contrib.messages.context_processors.messages', | ||
| 67 | ], | ||
| 68 | }, | ||
| 69 | }, | ||
| 70 | ] | ||
| 71 | |||
| 72 | WSGI_APPLICATION = 'chat.wsgi.application' | ||
| 73 | ASGI_APPLICATION = 'chat.asgi.application' | ||
| 74 | |||
| 75 | |||
| 76 | # Database | ||
| 77 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases | ||
| 78 | |||
| 79 | DATABASES = { | ||
| 80 | 'default': { | ||
| 81 | 'ENGINE': 'django.db.backends.sqlite3', | ||
| 82 | 'NAME': BASE_DIR / 'db.sqlite3', | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | |||
| 87 | # Password validation | ||
| 88 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators | ||
| 89 | |||
| 90 | AUTH_PASSWORD_VALIDATORS = [ | ||
| 91 | { | ||
| 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', | ||
| 93 | }, | ||
| 94 | { | ||
| 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', | ||
| 96 | }, | ||
| 97 | { | ||
| 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', | ||
| 99 | }, | ||
| 100 | { | ||
| 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', | ||
| 102 | }, | ||
| 103 | ] | ||
| 104 | |||
| 105 | |||
| 106 | # Internationalization | ||
| 107 | # https://docs.djangoproject.com/en/5.0/topics/i18n/ | ||
| 108 | |||
| 109 | LANGUAGE_CODE = 'en-us' | ||
| 110 | |||
| 111 | TIME_ZONE = 'UTC' | ||
| 112 | |||
| 113 | USE_I18N = True | ||
| 114 | |||
| 115 | USE_TZ = True | ||
| 116 | |||
| 117 | |||
| 118 | # Static files (CSS, JavaScript, Images) | ||
| 119 | # https://docs.djangoproject.com/en/5.0/howto/static-files/ | ||
| 120 | |||
| 121 | STATIC_URL = 'static/' | ||
| 122 | |||
| 123 | # Default primary key field type | ||
| 124 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field | ||
| 125 | |||
| 126 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' | ||
| 127 | |||
| 128 | CHANNEL_LAYERS = { | ||
| 129 | "default": { | ||
| 130 | "BACKEND": "channels_redis.core.RedisChannelLayer", | ||
| 131 | "CONFIG": { | ||
| 132 | "hosts": [("127.0.0.1", 6379)], | ||
