added channels basic room consumer, minor updates for base game logic

This commit is contained in:
Alexander Karpov 2022-06-21 11:36:55 +03:00
parent e75573b3cd
commit 52a8608198
9 changed files with 203 additions and 90 deletions

View File

@ -9,12 +9,21 @@ https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
import os import os
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import OriginValidator
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter from channels.routing import ProtocolTypeRouter, URLRouter
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chess_backend.settings') import room.routing
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chess_backend.settings")
"http": django_asgi_app,
}) application = ProtocolTypeRouter(
{
"http": get_asgi_application(),
"websocket": OriginValidator(
AuthMiddlewareStack(URLRouter(room.routing.websocket_urlpatterns)),
["*"],
),
}
)

View File

@ -1,39 +1,19 @@
"""
Django settings for chess_backend project.
Generated by 'django-admin startproject' using Django 4.0.3.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os import os
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-%_8sy196w4hzo9^cp9(@r=i+amh47r4mxfhq_(ok&=c(@%bhmk" SECRET_KEY = "django-insecure-%_8sy196w4hzo9^cp9(@r=i+amh47r4mxfhq_(ok&=c(@%bhmk"
TOKEN_EXP = 2678400 # 31 day TOKEN_EXP = 2678400 # 31 day
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
# Packages # Packages
@ -45,36 +25,26 @@ INSTALLED_APPS = [
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
ROOT_URLCONF = "chess_backend.urls" ROOT_URLCONF = "chess_backend.urls"
TEMPLATES = [ ASGI_APPLICATION = "chess_backend.asgi.application"
{ CHANNEL_LAYERS = {
"BACKEND": "django.template.backends.django.DjangoTemplates", "default": {
"DIRS": [BASE_DIR / "templates"], "BACKEND": "channels_redis.core.RedisChannelLayer",
"APP_DIRS": True, "CONFIG": {
"OPTIONS": { "hosts": [("127.0.0.1", 6379)],
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
}, },
}, },
] }
ASGI_APPLICATION = 'chess_backend.asgi.application'
WSGI_APPLICATION = "chess_backend.wsgi.application" WSGI_APPLICATION = "chess_backend.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = { DATABASES = {
"default": { "default": {
@ -83,30 +53,12 @@ DATABASES = {
} }
} }
# Password validation LANGUAGES = [
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators ("en-us", "English"),
("ru", "Russian"),
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
] ]
# Internationalization TIME_ZONE = "Europe/Moscow"
# https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
@ -122,7 +74,23 @@ else:
MEDIA_ROOT = "/var/www/media/" MEDIA_ROOT = "/var/www/media/"
STATIC_ROOT = "/var/www/static/" STATIC_ROOT = "/var/www/static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"file": {
"level": "WARNING",
"class": "logging.FileHandler",
"filename": "debug.log",
},
},
"loggers": {
"django": {
"handlers": ["file"],
"level": "DEBUG",
"propagate": True,
},
},
}

View File

@ -1,25 +1,9 @@
"""chess_backend URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include from django.urls import path, include
urlpatterns = ( urlpatterns = (
[path("admin/", admin.site.urls), path("api/", include("game.urls"))] [path("api/", include("game.urls"))]
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
) )

View File

@ -103,13 +103,13 @@ class Hero(models.Model):
"HeroImageSet", on_delete=models.CASCADE, related_name="die_image_fkey" "HeroImageSet", on_delete=models.CASCADE, related_name="die_image_fkey"
) )
health = models.IntegerField( health = models.IntegerField(
validators=[MinValueValidator(0), MaxValueValidator(10)], blank=False validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
) )
attack = models.IntegerField( attack = models.IntegerField(
validators=[MinValueValidator(0), MaxValueValidator(10)], blank=False validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
) )
speed = models.IntegerField( speed = models.IntegerField(
validators=[MinValueValidator(0), MaxValueValidator(10)], blank=False validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
) )
def idle_img(self): def idle_img(self):

View File

@ -1,7 +1,46 @@
asgiref==3.5.2 asgiref==3.5.2
asttokens==2.0.5
attrs==21.4.0
autobahn==22.5.1
Automat==20.2.0
backcall==0.2.0
cffi==1.15.0
channels==3.0.4
constantly==15.1.0
cryptography==37.0.2
daphne==3.0.2
decorator==5.1.1
Django==4.0.5 Django==4.0.5
djangorestframework==3.13.1 djangorestframework==3.13.1
executing==0.8.3
hyperlink==21.0.0
idna==3.3
incremental==21.3.0
ipython==8.4.0
jedi==0.18.1
matplotlib-inline==0.1.3
parso==0.8.3
pexpect==4.8.0
pickleshare==0.7.5
Pillow==9.1.1 Pillow==9.1.1
pytz==2022.1 prompt-toolkit==3.0.29
sqlparse==0.4.2 ptyprocess==0.7.0
pure-eval==0.2.2
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycparser==2.21
Pygments==2.12.0
PyJWT==2.4.0 PyJWT==2.4.0
pyOpenSSL==22.0.0
pytz==2022.1
service-identity==21.1.0
six==1.16.0
sqlparse==0.4.2
stack-data==0.2.0
traitlets==5.2.2.post1
Twisted==22.4.0
txaio==22.2.1
typing_extensions==4.2.0
wcwidth==0.2.5
zope.interface==5.4.0
channels_redis

63
room/consumers.py Normal file
View File

@ -0,0 +1,63 @@
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class QueConsumer(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(args, kwargs)
self.room_group_name = None
async def connect(self):
self.room_group_name = "que"
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
# Receive message from WebSocket
async def receive(self, text_data):
print(text_data)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{"type": "chat_message", "message": text_data},
)
class RoomConsumer(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.room_group_name = None
self.room_name = None
async def connect(self):
self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
self.room_group_name = "room_%s" % self.room_name
# Join room group
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
# Receive message from WebSocket
async def receive(self, text_data):
print(text_data)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{"type": "chat_message", "message": text_data},
)
# Receive message from room group
async def chat_message(self, event):
message = event["message"]
# Send message to WebSocket
await self.send(text_data=json.dumps({"lot": message}))

32
room/middleware.py Normal file
View File

@ -0,0 +1,32 @@
from channels.db import database_sync_to_async
from django.core.exceptions import PermissionDenied
from game.models import Player
from game.services.jwt import read_jwt
@database_sync_to_async
def get_player(jwt):
payload = read_jwt(jwt)
if not payload:
raise PermissionDenied
try:
return Player.objects.get(id=payload)
except User.DoesNotExist:
return AnonymousUser()
class QueryAuthMiddleware:
"""Custom middleware to read user auth token from string."""
def __init__(self, app):
# Store the ASGI application we were passed
self.app = app
async def __call__(self, scope, receive, send):
# Look up user from query string (you should also do things like
# checking if it is a valid user ID, or if scope["user"] is already
# populated).
scope["user"] = await get_user(int(scope["query_string"]))
return await self.app(scope, receive, send)

View File

@ -1,3 +1,13 @@
from django.db import models from django.db import models
# Create your models here. # Create your models here.
from game.models import Player
class PlayerInQue(models.Model):
# TODO use redis for storing
player = models.ForeignKey(Player, unique=True, on_delete=models.CASCADE)
score = models.IntegerField()
def __str__(self):
return f"{self.player.name} in que with score {self.score}"

8
room/routing.py Normal file
View File

@ -0,0 +1,8 @@
from django.urls import path
from . import consumers
websocket_urlpatterns = [
path("room", consumers.QueConsumer.as_asgi()),
path("room/<str:room_name>/", consumers.RoomConsumer.as_asgi()),
]