diff --git a/indianpong/indianpong/routing.py b/indianpong/indianpong/routing.py index 6adae2c..121cf1f 100644 --- a/indianpong/indianpong/routing.py +++ b/indianpong/indianpong/routing.py @@ -1,10 +1,10 @@ - -from django.urls import re_path - -from pong import consumers - -websocket_urlpatterns = [ - re_path(r'ws/pong/$', consumers.PongConsumer.as_asgi()), - re_path(r'^ws/chat/(?P[\w-]+)/$', consumers.ChatConsumer.as_asgi()), -] - + +from django.urls import re_path + +from pong import consumers + +websocket_urlpatterns = [ + re_path(r'ws/pong/$', consumers.PongConsumer.as_asgi()), + re_path(r'^ws/chat/(?P[\w-]+)/$', consumers.PongConsumer.as_asgi()), +] + diff --git a/indianpong/indianpong/settings.py b/indianpong/indianpong/settings.py index 0c38655..8e0f974 100644 --- a/indianpong/indianpong/settings.py +++ b/indianpong/indianpong/settings.py @@ -1,151 +1,166 @@ -""" -Django settings for indianpong project. - -Generated by 'django-admin startproject' using Django 5.0. - -For more information on this file, see -https://docs.djangoproject.com/en/5.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/5.0/ref/settings/ -""" - -from pathlib import Path -from os import environ - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = environ.get("SECRET_KEY", default="w^bxst+y6yv=d*5+7h)2s3)5vfz!b2jayit+#1epn(gr1-fotw") - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = environ.get("DEBUG", default=True) - -ALLOWED_HOSTS = []#environ.get("ALLOWED_HOSTS", default="").split(" ") - - -# Application definition - -INSTALLED_APPS = [ - 'daphne', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'pong', -] - -CHANNEL_LAYERS = { - 'default': { - 'BACKEND': 'channels.layers.InMemoryChannelLayer', - }, -} - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'indianpong.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'indianpong.wsgi.application' -ASGI_APPLICATION = 'indianpong.asgi.application' - - -# Database -# https://docs.djangoproject.com/en/5.0/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} - -""" DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': environ.get("DB_NAME", default="transcendence"), - 'USER': environ.get("DB_USER"), - 'PASSWORD': environ.get("DB_PASSWORD"), - 'HOST': environ.get("DB_HOST", default="localhost"), - 'PORT': environ.get("DB_PORT", default="5432") - } -} """ - -AUTH_USER_MODEL = "pong.UserProfile" - -# Password validation -# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators - -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 -# https://docs.djangoproject.com/en/5.0/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/5.0/howto/static-files/ - -STATIC_URL = '/static/' -STATICFILES_DIRS = [ - BASE_DIR / 'static', -] - -# Default primary key field type -# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - -MEDIA_URL = "/media/" -MEDIA_ROOT = BASE_DIR / "media" +""" +Django settings for indianpong project. + +Generated by 'django-admin startproject' using Django 5.0. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +from pathlib import Path +from os import environ + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = environ.get("SECRET_KEY", default="w^bxst+y6yv=d*5+7h)2s3)5vfz!b2jayit+#1epn(gr1-fotw") + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = environ.get("DEBUG", default=True) + +ALLOWED_HOSTS = []#environ.get("ALLOWED_HOSTS", default="").split(" ") + + +# Application definition + +INSTALLED_APPS = [ + 'daphne', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'pong', +] + +CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels.layers.InMemoryChannelLayer', + }, +} + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'indianpong.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'pong.context_processors.userinfo', + ], + }, + }, +] + +WSGI_APPLICATION = 'indianpong.wsgi.application' +ASGI_APPLICATION = 'indianpong.asgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + +""" DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': environ.get("DB_NAME", default="transcendence"), + 'USER': environ.get("DB_USER"), + 'PASSWORD': environ.get("DB_PASSWORD"), + 'HOST': environ.get("DB_HOST", default="localhost"), + 'PORT': environ.get("DB_PORT", default="5432") + } +} """ + +AUTH_USER_MODEL = "pong.UserProfile" +LOGIN_URL = "login" + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +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 +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'Europe/Istanbul' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = '/static/' +STATICFILES_DIRS = [ + BASE_DIR / 'static', +] + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +MEDIA_URL = "/media/" +MEDIA_ROOT = BASE_DIR / "media" + +EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' +EMAIL_FILE_PATH = BASE_DIR / 'sent_emails' +EMAIL_HOST_USER = 'indianpong@gmail.com' + +""" EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = 587 +EMAIL_HOST_USER = environ.get("EMAIL_HOST_USER", default="indianpong@gmail.com") +EMAIL_HOST_PASSWORD = environ.get("EMAIL_HOST_PASSWORD", default="hxog cqpq jltp xjhi") +DEFAULT_FROM_EMAIL = EMAIL_HOST_USER +EMAIL_USE_TLS = True +EMAIL_USE_SSL = False """ \ No newline at end of file diff --git a/indianpong/indianpong/urls.py b/indianpong/indianpong/urls.py index 47007fc..2e5d84a 100644 --- a/indianpong/indianpong/urls.py +++ b/indianpong/indianpong/urls.py @@ -1,52 +1,65 @@ -""" -URL configuration for indianpong project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/5.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.urls.static import static -from django.contrib import admin -from django.urls import include, path -from pong.views import auth_callback, chat, rankings, dashboard, game, index, auth, profile_view, search, signup, login_view, logout_view, update_profile, create_tournament, create_tournament_match, start_chat, room - -urlpatterns = [ - path('admin/', admin.site.urls), - path('', index, name='index'), - path('signup', signup, name='signup'), - path('login', login_view, name='login'), - path('auth', auth, name='auth'), - path('auth_callback', auth_callback, name='auth_callback'), - path('logout', logout_view, name='logout'), - - path('chat/', chat, name='chat'), - path("start_chat/", start_chat, name="start_chat"), - path("chat//", room, name="room"), - #path('chat_room', chat_room, name='chat_room'), - - path('dashboard', dashboard, name='dashboard'), - path('rankings', rankings, name='rankings'), - path('search', search, name='search'), - path('game', game, name='game'), - path('profile/', profile_view, name='profile'), - path('update_profile', update_profile, name='update_profile'), - path('create_tournament', create_tournament, name='create_tournament'), - path('create_tournament_match', create_tournament_match, name='create_tournament_match'), - #path('setup_two_factor_auth', setup_two_factor_auth, name='setup_two_factor_auth'), - #path('generate_jwt_token', generate_jwt_token, name='generate_jwt_token'), -] - -handler404 = 'pong.views.handler404' - -if settings.DEBUG: +""" +URL configuration for indianpong project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.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.urls.static import static +from django.contrib import admin +from django.urls import include, path +from pong.views import aboutus, update_winner, inventory, store, activate_account, play_ai, pong_game_find, rps_game_find, auth_callback, chat, friends, match_history, password_change, password_reset, password_reset_done, rankings, dashboard, game, index, auth, chat_room, profile_view, search, set_password, signup, login_view, logout_view, profile_settings, setup_two_factor_auth, generate_jwt_token, create_tournament, create_tournament_match, start_chat, room + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', index, name='index'), + path('signup', signup, name='signup'), + path('activate//', activate_account, name='activate'), + path('login', login_view, name='login'), + path('auth', auth, name='auth'), + path('auth_callback', auth_callback, name='auth_callback'), + path('logout', logout_view, name='logout'), + path('rps-game-find', rps_game_find, name='rps_game_find'), + path('pong-game-find', pong_game_find, name='pong_game_find'), + path('play-ai', play_ai, name='play_ai'), + path('chat', chat, name='chat'), + path("start_chat/", start_chat, name="start_chat"), + path("chat//", room, name="room"), + #path('chat_room', chat_room, name='chat_room'), + path('dashboard', dashboard, name='dashboard'), + path('friends/', friends, name='friends'), + path('match-history/', match_history, name='match_history'), + path('about-us', aboutus, name='aboutus'), + path('rankings', rankings, name='rankings'), + path('inventory//', inventory, name='inventory'), + path('store//', store, name='store'), + path('search', search, name='search'), + path('pong-game', game, name='game'), + path('update_winner', update_winner, name='update_winner'), + path('profile/', profile_view, name='profile'), + path('profile//settings', profile_settings, name='profile_settings'), + path('password_change', password_change, name='password_change'), + path('password_reset', password_reset, name='password_reset'), + path('password_reset_confirm///', password_reset, name='password_reset_confirm'), + path('password_reset_done', password_reset_done, name='password_reset_done'), + path('set_password///', set_password, name='set_password'), + path('setup_two_factor_auth', setup_two_factor_auth, name='setup_two_factor_auth'), + path('generate_jwt_token', generate_jwt_token, name='generate_jwt_token'), + path('create_tournament', create_tournament, name='create_tournament'), + path('create_tournament_match', create_tournament_match, name='create_tournament_match'), +] + +handler404 = 'pong.views.handler404' + +if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/indianpong/pong/admin.py b/indianpong/pong/admin.py index 259f5b6..b00a7d3 100644 --- a/indianpong/pong/admin.py +++ b/indianpong/pong/admin.py @@ -1,61 +1,115 @@ -from django.contrib import admin -from .models import UserProfile, Tournament, TournamentMatch, Room, Message -from django.utils.html import format_html - -@admin.register(UserProfile) -class UserProfileAdmin(admin.ModelAdmin): - """ - Admin class for managing user profiles. - - Attributes: - list_display (tuple): A tuple of fields to be displayed in the admin list view. - search_fields (tuple): A tuple of fields to be used for searching in the admin list view. - fieldsets (tuple): A tuple of fieldsets to be displayed in the admin edit view. - """ - - list_display = ('username', 'email', 'displayname', 'avatar_thumbnail', 'wins', 'losses') - search_fields = ('username', 'email', 'displayname') - fieldsets = ( - ('User Information', {'fields': ('username', 'password', 'displayname', 'email', 'avatar', 'friends' )}), - ('Dates', {'fields': ('date_joined', 'last_login')}), - ('Roles', {'fields': ('is_staff', 'is_active', 'is_superuser')}), - ('Permissions', {'fields': ('groups', 'user_permissions')}), - ('Stats', {'fields': ('wins', 'losses')}), - ) - - def avatar_thumbnail(self, obj): - return format_html(obj.thumbnail) - avatar_thumbnail.short_description = 'Avatar' - -@admin.register(Tournament) -class TournamentAdmin(admin.ModelAdmin): - list_display = ('name', 'start_date') - search_fields = ('name',) - -@admin.register(TournamentMatch) -class TournamentMatchAdmin(admin.ModelAdmin): - list_display = ('tournament', 'player1', 'player2', 'winner') - search_fields = ('tournament__name', 'player1__username', 'player2__username', 'winner__username') - -@admin.register(Room) -class RoomAdmin(admin.ModelAdmin): - list_display = ["first_user", "second_user"] - -@admin.register(Message) -class MessageAdmin(admin.ModelAdmin): - list_display = ["user", "room", "created_date"] - -""" @admin.register(TwoFactorAuth) -class TwoFactorAuthAdmin(admin.ModelAdmin): - list_display = ('user', 'is_enabled') - search_fields = ('user__username',) - -@admin.register(JWTToken) -class JWTTokenAdmin(admin.ModelAdmin): - list_display = ('user', 'token', 'expires_at') - search_fields = ('user__username',) - -@admin.register(OAuthToken) -class OAuthTokenAdmin(admin.ModelAdmin): - list_display = ('user', 'access_token', 'refresh_token', 'expires_at') - search_fields = ('user__username',) """ +from django import forms +from django.contrib import admin +from .models import OAuthToken, Social, UserItem, StoreItem, UserProfile, Tournament, Room, Message, Game, UserGameStat +from django.utils.html import format_html +from django.forms import ModelChoiceField + + +@admin.register(UserProfile) +class UserProfileAdmin(admin.ModelAdmin): + """ + Admin class for managing user profiles. + + Attributes: + list_display (tuple): A tuple of fields to be displayed in the admin list view. + search_fields (tuple): A tuple of fields to be used for searching in the admin list view. + fieldsets (tuple): A tuple of fieldsets to be displayed in the admin edit view. + """ + + list_display = ('username', 'email', 'displayname', 'avatar_thumbnail') + search_fields = ('username', 'email', 'displayname') + fieldsets = ( + ('User Information', {'fields': ('username', 'password', 'displayname', 'email', 'avatar', 'friends', 'elo_point', 'indian_wallet')}), + ('Dates', {'fields': ('date_joined', 'last_login')}), + ('Roles', {'fields': ('is_staff', 'is_active', 'is_superuser', 'is_verified', 'is_42student')}), + ('Permissions', {'fields': ('groups', 'user_permissions')}), + ) + + def avatar_thumbnail(self, obj): + return format_html(obj.thumbnail) + avatar_thumbnail.short_description = 'Avatar' + +@admin.register(StoreItem) +class StoreAdmin(admin.ModelAdmin): + list_display = ('category_name', 'name', 'image_url', 'description', 'price', 'show_status') + search_fields = ('name', 'description') + + def __str__(self): + return self.name + +class UserItemForm(forms.ModelForm): + class Meta: + model = UserItem + fields = ('user', 'item', 'whatis', 'is_bought', 'is_equipped') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['item'].label_from_instance = lambda obj: obj.name + +@admin.register(UserItem) +class UserItemAdmin(admin.ModelAdmin): + form = UserItemForm + list_display = ('user', 'get_item_name', 'whatis', 'is_bought', 'is_equipped') + list_filter = ('is_equipped',) + search_fields = ('user__username', 'item__name') + + def get_item_name(self, obj): + return obj.item.name + + get_item_name.short_description = 'Item Name' + +@admin.register(UserGameStat) +class UserGameStatAdmin(admin.ModelAdmin): + list_display = ('get_user', 'total_games_pong', 'total_win_pong', 'total_lose_pong', 'total_win_streak_pong', 'total_win_rate_pong', 'total_lose_streak_pong', 'total_avg_game_duration', 'total_avg_points_won', 'total_avg_points_lost') + search_fields = ('userprofile__username', 'total_win_pong',) + + def get_user(self, obj): + return obj.userprofile + get_user.short_description = 'User' + +@admin.register(Social) +class SocialAdmin(admin.ModelAdmin): + list_display = ('get_user', 'stackoverflow', 'github', 'twitter', 'instagram') + + def get_user(self, obj): + return obj.userprofile + get_user.short_description = 'User' + + search_fields = ('userprofile__username', 'stackoverflow', 'github', 'twitter', 'instagram') + + +@admin.register(Game) +class GameAdmin(admin.ModelAdmin): + list_display = ('group_name', 'player1', 'player2', 'player1_score', 'player2_score', 'created_at', 'game_duration', 'winner', 'loser') + list_filter = ('player1', 'player2', 'winner', 'loser') + search_fields = ('player1__username', 'player2__username', 'group_name') + +@admin.register(Tournament) +class TournamentAdmin(admin.ModelAdmin): + list_display = ('name', 'start_date') + search_fields = ('name',) + +@admin.register(Room) +class RoomAdmin(admin.ModelAdmin): + list_display = ["first_user", "second_user"] + +@admin.register(Message) +class MessageAdmin(admin.ModelAdmin): + list_display = ["user", "room", "created_date"] + + +@admin.register(OAuthToken) +class OAuthTokenAdmin(admin.ModelAdmin): + list_display = ('user', 'access_token', 'refresh_token', 'expires_in', 'created_at', 'secret_valid_until') + search_fields = ('user__username',) + +""" @admin.register(TwoFactorAuth) +class TwoFactorAuthAdmin(admin.ModelAdmin): + list_display = ('user', 'is_enabled') + search_fields = ('user__username',) + +@admin.register(JWTToken) +class JWTTokenAdmin(admin.ModelAdmin): + list_display = ('user', 'token', 'expires_at') + search_fields = ('user__username',) +""" diff --git a/indianpong/pong/consumers.py b/indianpong/pong/consumers.py index e73bc21..b1f924a 100644 --- a/indianpong/pong/consumers.py +++ b/indianpong/pong/consumers.py @@ -1,736 +1,758 @@ -import json -from channels.generic.websocket import AsyncWebsocketConsumer -from channels.db import database_sync_to_async -from pong.utils import AsyncLockedDict -from .models import Game, Tournament, MatchRecord, UserProfile, Room, Message #Match, Score, chat -from pong.game import * - -USER_CHANNEL_NAME = AsyncLockedDict() # key: username, value: channel_name -USER_STATUS = AsyncLockedDict() # key: username, value: game_id or online -GAMES = AsyncLockedDict() # key: game_id, value: PongGame object - -class PongConsumer(AsyncWebsocketConsumer): - async def connect(self): - # Get the user from the scope - self.user = self.scope["user"] - # Check if the user is authenticated - if self.user.is_anonymous: - # Reject the connection - await self.close() - # Set the user's channel name - await USER_CHANNEL_NAME.set(self.user.username, self.channel_name) - #self.user.channel_name = self.channel_name - #await self.user.asave() - # Accept the connection - await self.accept() - # Add the user to the 'online' group - await self.channel_layer.group_add("online", self.channel_name) - # Set the user's status to 'online' - await USER_STATUS.set(self.user.username, 'online') - # Send a message to the group with the user's username - online_users = await USER_STATUS.get_keys_with_value('online') - await self.channel_layer.group_send( - "online", - { - "type": "user.online", - "username": self.user.username, - "users": online_users, - } - ) - - async def disconnect(self, close_code): - #self.user.channel_name = None - game_id = await USER_STATUS.get(self.user.username) - if game_id != 'online' and game_id != None: - # Check if user's username is in any game and that game is not ended - game = await GAMES.get(game_id) - if game.status == Status.STARTED: # 2 means game is started - await self.record_for_disconnected(game_id, game) - # Delete game cache - await GAMES.delete(game_id) - # Remove both users from the game group - player1_channel_name = await USER_CHANNEL_NAME.get(game.player1.username) - player2_channel_name = await USER_CHANNEL_NAME.get(game.player2.username) - await self.channel_layer.group_discard(game.group_name, player1_channel_name) - await self.channel_layer.group_discard(game.group_name, player2_channel_name) - # Remove the user's channel name - await USER_CHANNEL_NAME.delete(self.user.username) - # make it offline - await USER_STATUS.delete(self.user.username) - # Remove the user from the 'online' group - await self.channel_layer.group_discard("online", self.channel_name) - # Send a message to the group with the user's username - await self.channel_layer.group_send( - "online", - { - "type": "user.offline", - "username": self.user.username, - } - ) - - async def receive(self, text_data): - # Receive a message from the client - data = json.loads(text_data) - action = data["action"] - if action == "invite": #* Validated - # Invite another user to play a game - invited = data["invited"] - # Check if the opponent exists #TODO remove these when you implement button invite - if invited == self.user.username: - await self.send(text_data=json.dumps({ - "error": "You can't invite yourself", - })) - return - if not await self.check_is_user_exist(invited): - return - #Check if the opponent is online - if not await self.check_is_user_online(invited): - return - # Get the invited channel name - invited_channel_name = await USER_CHANNEL_NAME.get(invited) - # Set game group name - group_name = self.user.username + "-" + invited #str(uuid4()) - # Add both users to the game group - await self.channel_layer.group_add(group_name, self.channel_name) - await self.channel_layer.group_add(group_name, invited_channel_name) - # Send a message to the opponent with the invitation - await self.channel_layer.group_send( - group_name, - { - "type": "game.invite", - "group_name": group_name, - "inviter": self.user.username, - "invited": invited, - } - ) - elif action == "accept": #? Needs validation - # Accept an invitation to play a game - group_name = data["group_name"] - accepted = data["accepted"] - accepter = data["accepter"] - # Create a new game instance and save it to the database - game = await self.create_game(group_name, accepted, accepter) - # Create a new game instance and save it to the cache - await GAMES.set(game.id, PongGame(accepted, accepter)) - # Send a message to the game group with the game id and the players' usernames - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.accept", - "game_id": game.id, - "accepter": accepter, - "accepted": accepted - } - ) - elif action == "decline": #? Needs validation - # Decline an invitation to play a game - group_name = data["group_name"] - declined = data["declined"] - decliner = data["decliner"] - declined_channel_name = await USER_CHANNEL_NAME.get(declined) - # Send a message to the game group with the game id - await self.channel_layer.group_send( - group_name, - { - "type": "game.decline", - "decliner": decliner, - "declined": declined, - } - ) - # Discard both from the game group - await self.channel_layer.group_discard(group_name, self.channel_name) - await self.channel_layer.group_discard(group_name, declined_channel_name) - elif action == "start.request": #? Needs validation - # Start a game - game_id = data["game_id"] - player1 = data["player1"] - player2 = data["player2"] - vote = data["vote"] - # Get the current game status and update it with the vote count - game = await GAMES.get(game_id) - current = game.status.value + int(vote) - await GAMES.set_field_value(game_id, Status(current), "status") - # Check both players voted to start the game - if Status(current) == Status.STARTED: # both players voted to start the game - # Update the players' status to game_id and save them to cache - await USER_STATUS.set(player1, game_id) - await USER_STATUS.set(player2, game_id) - # Send a message to the game group with the game id - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.start", - "game_id": game_id, - "player1": player1, - "player2": player2, - "vote": current, - } - ) - elif action == "leave.game": #? Needs validation - # Leave a game - game_id = data["game_id"] - left = data["left"] - opponent = data["opponent"] - # Get scores - game = await GAMES.get(game_id) - left_score = game.getScore(left) # blocking? - opponent_score = MAX_SCORE # set max score automaticaly - # Record the game - await self.record_game(game_id, left_score, opponent_score, opponent) - # Send a message to the game group with the game id and the opponent's username - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.leave", - "game_id": game_id, - "left_score": left_score, - "opponent_score": opponent_score, - "winner": opponent, - } - ) - # Discard both from the game group - standing_channel_name = await USER_CHANNEL_NAME.get(opponent) - await self.channel_layer.group_discard(game.group_name, self.channel_name) - await self.channel_layer.group_discard(game.group_name, standing_channel_name) - # Update the game status to 'ended' or delete it - await GAMES.delete(game_id) - elif action == "ball": #? Needs validation - # Make a move in a game # TODO this should be in server side not client side? - # Interval should be 16 ms so every 16 ms - # we should send a message to the clients with the ball coordinates - game_id = data["game_id"] - # Move and Get ball coordinates - game = await GAMES.get(game_id) #? When games status is ended, game_id is deleted from GAMES cache - if (game != None): #? So game becomes None. Is this check enough? or moving delete to end solve without this - if (game.status == Status.STARTED): - x, y, player1_score, player2_score = game.moveBall() - # Send a message to the game group with the game id, the move coordinates - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.ball", - "game_id": game_id, - "x": x, - "y": y, - "player1_score": player1_score, - "player2_score": player2_score, - } - ) - elif (game.status == Status.ENDED): - # Get scores - player1_score = game.player1.score - player2_score = game.player2.score - winner = player1_score > player2_score and game.player1.username or game.player2.username - # Set the game winner, scores and save it to the database - await self.record_game(game_id, player1_score, player2_score, winner) - # Send a message to the game group with the game id and the winner's username - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.end", - "game_id": game_id, - "player1_score": player1_score, #? maybe redundant - "player2_score": player2_score, - "winner": winner, - } - ) - # Set the game status to 'ended' or delete it - await GAMES.delete(game_id) - elif action == "paddle": #? Needs validation - # Make a move in a game - game_id = data["game_id"] - dir = data["direction"] - # Move and Get paddle coordinate - game = await GAMES.get(game_id) #? When games status is ended, game_id is deleted from GAMES cache - if (game != None): #? So game becomes None. Is this check enough? or moving delete to end solve without this - y = game.movePaddle(self.user.username, dir) - # Send a message to the game group with the game id, the paddle coordinate, and the player's username - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.paddle", - "game_id": game_id, - "y": y, - "player": self.user.username, - } - ) - ''' - elif action == "create": - # Create a new tournament - name = data["name"] - #start_date = data["start_date"] - #end_date = data["end_date"] - # Create a new tournament instance with the given details and save it to the database - tournament = await self.create_tournament(name) - # Send a message to the 'online' group with the tournament id and the tournament details - await self.channel_layer.group_send( - "online", - { - "type": "tournament.create", - "tournament_id": tournament.id, - "name": tournament.name, - "start_date": tournament.start_date, - #"end_date": tournament.end_date, - } - ) - elif action == "join": - # Join a tournament - tournament_id = data["tournament_id"] - # Get the tournament instance from the database - tournament = await self.get_tournament(tournament_id) - # Add the user to the tournament players and save it to the database - tournament.participants.add(self.user) - await self.save_tournament(tournament) - # Send a message to the 'online' group with the tournament id and the user's username - await self.channel_layer.group_send( - "online", - { - "type": "tournament.join", - "tournament_id": tournament.id, - "username": self.user.username, - } - ) - elif action == "leave": - # Leave a tournament - tournament_id = data["tournament_id"] - # Get the tournament instance from the database - tournament = await self.get_tournament(tournament_id) - # Remove the user from the tournament players and save it to the database - tournament.participants.remove(self.user) - await self.save_tournament(tournament) - # Send a message to the 'online' group with the tournament id and the user's username - await self.channel_layer.group_send( - "online", - { - "type": "tournament.leave", - "tournament_id": tournament.id, - "username": self.user.username, - } - ) - elif action == "cancel": - # Cancel a tournament - tournament_id = data["tournament_id"] - # Get the tournament instance from the database - tournament = await self.get_tournament(tournament_id) - # Delete the tournament instance from the database - await tournament.delete() - # Send a message to the 'online' group with the tournament id - await self.channel_layer.group_send( - "online", - { - "type": "tournament.cancel", - "tournament_id": tournament.id, - } - ) - elif action == "start": - # Start a tournament - tournament_id = data["tournament_id"] - # Get the tournament instance from the database - tournament = await self.get_tournament(tournament_id) - # Update the tournament status to 'started' and save it to the database - tournament.status = "started" - await self.save_tournament(tournament) - # Create the first round of matches and save them to the database - matches = await self.create_matches(tournament) - # Send a message to the 'online' group with the tournament id and the matches details - await self.channel_layer.group_send( - "online", - { - "type": "tournament.start", - "tournament_id": tournament.id, - "matches": [ - { - "match_id": match.id, - "player1": match.player1.username, - "player2": match.player2.username, - } - for match in matches - ], - } - ) - elif action == "finish": - # Finish a match in a tournament - tournament_id = data["tournament_id"] - match_id = data["match_id"] - winner = data["winner"] - # Get the match instance from the database - match = await self.get_match(match_id) - # Update the match status to 'finished' and save it to the database - match.status = "finished" - await self.save_match(match) - # Update the tournament standings with the match result and save it to the database - tournament = await self.get_tournament(tournament_id) - tournament.standings[winner] += 1 - await self.save_tournament(tournament) - # Send a message to the 'online' group with the match id and the winner's username - await self.channel_layer.group_send( - "online", - { - "type": "tournament.finish", - "match_id": match.id, - "winner": winner, - } - ) - elif action == "next": - # Start the next round of matches in a tournament - tournament_id = data["tournament_id"] - # Get the tournament instance from the database - tournament = await self.get_tournament(tournament_id) - # Check if the tournament is over - if len(tournament.standings) == 1: - # Update the tournament status to 'ended' and save it to the database - tournament.status = "ended" - await self.save_tournament(tournament) - # Send a message to the 'online' group with the tournament id and the winner's username - await self.channel_layer.group_send( - "online", - { - "type": "tournament.end", - "tournament_id": tournament.id, - "winner": list(tournament.standings.keys())[0], - } - ) - else: - # Create the next round of matches and save them to the database - matches = await self.create_matches(tournament) - # Send a message to the 'online' group with the tournament id and the matches details - await self.channel_layer.group_send( - "online", - { - "type": "tournament.next", - "tournament_id": tournament.id, - "matches": [ - { - "match_id": match.id, - "player1": match.player1.username, - "player2": match.player2.username, - } - for match in matches - ], - } - ) - ''' - # Helper methods to interact with the database - @database_sync_to_async - def get_online_users_list(self): - return [user.username for user in UserProfile.objects.filter(online=True)] - - async def create_game(self, group_name, player1, player2): - # Create a new game instance with the given players and an group_name - accepted = await UserProfile.objects.aget(username=player1) - accepter = await UserProfile.objects.aget(username=player2) - game = await Game.objects.acreate(group_name=group_name, player1=accepted, player2=accepter) - return game - - async def record_game(self, game_id, player1_score, player2_score, winner): - game = await Game.objects.aget(id=game_id) - await USER_STATUS.set(game.player1.username, 'online') #? - await USER_STATUS.set(game.player2.username, 'online') #? - game.player1_score = player1_score - game.player2_score = player2_score - game.winner = await UserProfile.objects.aget(username=winner) - await game.asave() - - async def record_for_disconnected(self, game_id, game): - if game.player1.username == self.user.username: - await self.record_game(game_id, game.player1.score, MAX_SCORE, game.player2.username) - else: - await self.record_game(game_id, MAX_SCORE, game.player2.score, game.player1.username) - - async def check_is_user_exist(self, username): - answer = await UserProfile.objects.filter(username=username).aexists() - if not answer: - await self.send(text_data=json.dumps({ - "error": "User doesn't exist", - })) - return answer - - async def check_is_user_online(self, username): - answer = await USER_STATUS.get(username) == 'online' - if not answer: - await self.send(text_data=json.dumps({ - "error": "User is already playing", - })) - return answer - - - - ''' - @database_sync_to_async - def create_tournament(self, name): - # Create a new tournament instance with the given name and an empty standings - # start_date in models.py is auto_now_add=True maybe we shouldn't pass it here - tournament = Tournament.objects.create(id=uuid4(), name=name, status="open", start_date=datetime.now(), standings={}) - return tournament - - @database_sync_to_async - def get_tournament(self, tournament_id): - # Get the tournament instance with the given id - tournament = Tournament.objects.get(id=tournament_id) - return tournament - - @database_sync_to_async - def save_tournament(self, tournament): - # Save the tournament instance to the database - tournament.save() - - @database_sync_to_async - def create_matches(self, tournament): - # Create the next round of matches for the tournament - # This is a simplified logic that assumes the number of players is a power of two - # and that the players are ordered by their standings - players = list(tournament.participants.all()) - matches = [] - for i in range(0, len(players), 2): - # Create a new match instance with the pair of players and the tournament - match = Match.objects.create(player1=players[i], player2=players[i+1], tournament=tournament) - matches.append(match) - return matches - - @database_sync_to_async - def get_match(self, match_id): - # Get the match instance with the given id - match = Match.objects.get(id=match_id) - return match - - @database_sync_to_async - def save_match(self, match): - # Save the match instance to the database - match.save() -''' - # Handler methods for different types of messages - async def user_online(self, event): - # Handle a message that a user is online - username = event["username"] - users = event["users"] - # Send a message to the client with the username - await self.send(text_data=json.dumps({ - "type": "user.online", - "username": username, - "users": users, - })) - - async def user_offline(self, event): - # Handle a message that a user is offline - username = event["username"] - #users = event["users"] - # Send a message to the client with the username - await self.send(text_data=json.dumps({ - "type": "user.offline", - "username": username, - #"users": users, - })) - - async def game_invite(self, event): - # Handle a message that a game is invited - group_name = event["group_name"] - inviter = event["inviter"] - invited = event["invited"] - # Send a message to the client with the game id and the players' usernames - await self.send(text_data=json.dumps({ - "type": "game.invite", - "group_name": group_name, - "inviter": inviter, - "invited": invited, - })) - - async def game_accept(self, event): - # Handle a message that a game is accepted - game_id = event["game_id"] - accepted = event["accepted"] - accepter = event["accepter"] - # Send a message to the client with the game id and the players' usernames - await self.send(text_data=json.dumps({ - "type": "game.accept", - "game_id": game_id, - "accepted": accepted, - "accepter": accepter, - })) - - async def game_decline(self, event): - # Handle a message that a game is declined - decliner = event["decliner"] - declined = event["declined"] - # Send a message to the client with the game id - await self.send(text_data=json.dumps({ - "type": "game.decline", - "declined": declined, - "decliner": decliner, - })) - - async def game_start(self, event): - # Handle a message that a vote is made to start a game - game_id = event["game_id"] - player1 = event["player1"] - player2 = event["player2"] - vote = event["vote"] - # Send a message to the client with the game id and the players' usernames - await self.send(text_data=json.dumps({ - "type": "game.start", - "game_id": game_id, - "player1": player1, - "player2": player2, - "vote": vote, - })) - - async def game_leave(self, event): - # Handle a message that a game is left - game_id = event["game_id"] - left_score = event["left_score"] - opponent_score = event["opponent_score"] - winner = event["winner"] - # Send a message to the client with the game id - await self.send(text_data=json.dumps({ - "type": "game.leave", - "game_id": game_id, - "left_score": left_score, - "opponent_score": opponent_score, - "winner": winner, - })) - - async def game_end(self, event): - # Handle a message that a game is ended - game_id = event["game_id"] - player1_score = event["player1_score"] - player2_score = event["player2_score"] - winner = event["winner"] - # Send a message to the client with the game id and the winner's username - await self.send(text_data=json.dumps({ - "type": "game.end", - "game_id": game_id, - "player1_score": player1_score, - "player2_score": player2_score, - "winner": winner, - })) - - async def game_ball(self, event): - # Handle a message that a ball move is made in a game - game_id = event["game_id"] - x = event["x"] - y = event["y"] - player1_score = event["player1_score"] - player2_score = event["player2_score"] - # Send a message to the client with the game id, the move coordinates, and the player's username - await self.send(text_data=json.dumps({ - "type": "game.ball", - "game_id": game_id, - "x": x, - "y": y, - "player1_score": player1_score, - "player2_score": player2_score, - })) - - async def game_paddle(self, event): - # Handle a message that a paddle move is made in a game - game_id = event["game_id"] - y = event["y"] - player = event["player"] - # Send a message to the client with the game id, the move coordinates, and the player's username - await self.send(text_data=json.dumps({ - "type": "game.paddle", - "game_id": game_id, - "y": y, - "player": player, - })) - -''' - async def tournament_join(self, event): - # Handle a message that a user joined a tournament - tournament_id = event["tournament_id"] - username = event["username"] - # Send a message to the client with the tournament id and the username - await self.send(text_data=json.dumps({ - "type": "tournament.join", - "tournament_id": tournament_id, - "username": username, - })) - - async def tournament_start(self, event): - # Handle a message that a tournament is started - tournament_id = event["tournament_id"] - matches = event["matches"] - # Send a message to the client with the tournament id and the matches details - await self.send(text_data=json.dumps({ - "type": "tournament.start", - "tournament_id": tournament_id, - "matches": matches, - })) - - async def tournament_finish(self, event): - # Handle a message that a match is finished in a tournament - match_id = event["match_id"] - winner = event["winner"] - # Send a message to the client with the match id and the winner's username - await self.send(text_data=json.dumps({ - "type": "tournament.finish", - "match_id": match_id, - "winner": winner, - })) - - async def tournament_next(self, event): - # Handle a message that the next round of matches is started in a tournament - tournament_id = event["tournament_id"] - matches = event["matches"] - # Send a message to the client with the tournament id and the matches details - await self.send(text_data=json.dumps({ - "type": "tournament.next", - "tournament_id": tournament_id, - "matches": matches, - })) - - async def tournament_end(self, event): - # Handle a message that a tournament is ended - tournament_id = event["tournament_id"] - winner = event["winner"] - # Send a message to the client with the tournament id and the winner's username - await self.send(text_data=json.dumps({ - "type": "tournament.end", - "tournament_id": tournament_id, - "winner": winner, - })) -''' - - -class ChatConsumer(AsyncWebsocketConsumer): - async def connect(self): - self.room_name = self.scope["url_route"]["kwargs"]["room_name"] - self.room_group_name = "chat_%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): - text_data_json = json.loads(text_data) - message = text_data_json["message"] - - user = await UserProfile.objects.aget(username = text_data_json["user"]) - m = await Message.objects.acreate(content=message, user=user, room_id=self.room_name) #? Room object isn't created yet - # Send message to room group - await self.channel_layer.group_send( - self.room_group_name, - { - "type": "chat_message", - "message": message, - "user": user.username, - "created_date": m.get_short_date(), #? blocking? - } - ) - - # Receive message from room group - async def chat_message(self, event): - message = event["message"] - user = event["user"] - created_date = event["created_date"] - # Send message to WebSocket - await self.send(text_data=json.dumps({ - "message": message, - "user": user, - "created_date": created_date, - })) \ No newline at end of file +import json +from channels.generic.websocket import AsyncWebsocketConsumer +from channels.db import database_sync_to_async +from pong.utils import AsyncLockedDict +from .models import Game, Tournament, UserProfile, Room, Message #Match, Score, chat +from pong.game import * + +USER_CHANNEL_NAME = AsyncLockedDict() # key: username, value: channel_name +USER_STATUS = AsyncLockedDict() # key: username, value: game_id or online +GAMES = AsyncLockedDict() # key: game_id, value: PongGame object + +class PongConsumer(AsyncWebsocketConsumer): + async def connect(self): + # Get the user from the scope + self.user = self.scope["user"] + self.room_group_name = None + # Check if the user is authenticated + if self.user.is_anonymous: + # Reject the connection + await self.close() + # Set the user's channel name + await USER_CHANNEL_NAME.set(self.user.username, self.channel_name) + #self.user.channel_name = self.channel_name + #await self.user.asave() + # Accept the connection + await self.accept() + # Add the user to the 'online' group + await self.channel_layer.group_add("online", self.channel_name) + # Set the user's status to 'online' + await USER_STATUS.set(self.user.username, 'online') + # Send a message to the group with the user's username + online_users = await USER_STATUS.get_keys_with_value('online') + await self.channel_layer.group_send( + "online", + { + "type": "user.online", + "username": self.user.username, + "users": online_users, + } + ) + + async def disconnect(self, close_code): + #self.user.channel_name = None + game_id = await USER_STATUS.get(self.user.username) + if game_id != 'online' and game_id != None: + # Check if user's username is in any game and that game is not ended + game = await GAMES.get(game_id) + if game.status == Status.STARTED: # 2 means game is started + await self.record_for_disconnected(game_id, game) + # Delete game cache + await GAMES.delete(game_id) + # Remove both users from the game group + player1_channel_name = await USER_CHANNEL_NAME.get(game.player1.username) + player2_channel_name = await USER_CHANNEL_NAME.get(game.player2.username) + await self.channel_layer.group_discard(game.group_name, player1_channel_name) + await self.channel_layer.group_discard(game.group_name, player2_channel_name) + # Remove the user's channel name + await USER_CHANNEL_NAME.delete(self.user.username) + # make it offline + await USER_STATUS.delete(self.user.username) + # Remove the user from the 'online' group + await self.channel_layer.group_discard("online", self.channel_name) + if self.room_group_name != None: + # Leave room group + await self.channel_layer.group_discard(self.room_group_name, self.channel_name) + # Send a message to the group with the user's username + await self.channel_layer.group_send( + "online", + { + "type": "user.offline", + "username": self.user.username, + } + ) + + async def receive(self, text_data): + # Receive a message from the client + data = json.loads(text_data) + action = data["action"] + + ### CHAT ### + if action == "room": + room_name = data["room_name"] + self.room_name = room_name + self.room_group_name = "chat_%s" % room_name + + # Join room group + await self.channel_layer.group_add(self.room_group_name, self.channel_name) + + elif action == "message": + user = data["user"] + message = data["message"] + # Create Message Object + room = await Room.objects.aget(room_name=self.room_name) + user = await UserProfile.objects.aget(username = user) + msg = await Message.objects.acreate(content=message, user=user, room=room) + # Send message to room group + await self.channel_layer.group_send( + self.room_group_name, + { + "type": "chat.message", + "message": message, + "user": user.username, + "created_date": msg.get_short_date(), + } + ) + ### GAME ### + elif action == "invite": #* Validated + # Invite another user to play a game + invited = data["invited"] + # Check if the opponent exists #TODO remove these when you implement button invite + if invited == self.user.username: + await self.send(text_data=json.dumps({ + "error": "You can't invite yourself", + })) + return + if not await self.check_is_user_exist(invited): + return + #Check if the opponent is online + if not await self.check_is_user_online(invited): + return + # Get the invited channel name + invited_channel_name = await USER_CHANNEL_NAME.get(invited) + # Set game group name + group_name = self.user.username + "-" + invited #str(uuid4()) + # Add both users to the game group + await self.channel_layer.group_add(group_name, self.channel_name) + await self.channel_layer.group_add(group_name, invited_channel_name) + # Send a message to the opponent with the invitation + await self.channel_layer.group_send( + group_name, + { + "type": "game.invite", + "group_name": group_name, + "inviter": self.user.username, + "invited": invited, + } + ) + elif action == "accept": #? Needs validation + # Accept an invitation to play a game + group_name = data["group_name"] + accepted = data["accepted"] + accepter = data["accepter"] + + await self.accept_handler(group_name, accepted, accepter) + + elif action == "decline": #? Needs validation + # Decline an invitation to play a game + group_name = data["group_name"] + declined = data["declined"] + decliner = data["decliner"] + declined_channel_name = await USER_CHANNEL_NAME.get(declined) + # Send a message to the game group with the game id + await self.channel_layer.group_send( + group_name, + { + "type": "game.decline", + "decliner": decliner, + "declined": declined, + } + ) + # Discard both from the game group + await self.channel_layer.group_discard(group_name, self.channel_name) + await self.channel_layer.group_discard(group_name, declined_channel_name) + elif action == "start.request": #? Needs validation + # Start a game + game_id = data["game_id"] + player1 = data["player1"] + player2 = data["player2"] + vote = data["vote"] + # Get the current game status and update it with the vote count + game = await GAMES.get(game_id) + current = game.status.value + int(vote) + await GAMES.set_field_value(game_id, Status(current), "status") + # Check both players voted to start the game + if Status(current) == Status.STARTED: # both players voted to start the game + # Update the players' status to game_id and save them to cache + await USER_STATUS.set(player1, game_id) + await USER_STATUS.set(player2, game_id) + # Send a message to the game group with the game id + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.start", + "game_id": game_id, + "player1": player1, + "player2": player2, + "vote": current, + } + ) + elif action == "leave.game": #? Needs validation + # Leave a game + game_id = data["game_id"] + left = data["left"] + opponent = data["opponent"] + await self.leave_handler(game_id, left, opponent) + + elif action == "ball": #? Needs validation + # Make a move in a game and get the ball coordinates + # we send a message to the clients with the ball coordinates + game_id = data["game_id"] + # Move and Get ball coordinates + game = await GAMES.get(game_id) #? When games status is ended, game_id is deleted from GAMES cache + if (game != None): #? So game becomes None. Is this check enough? or moving delete to end solve without this + if (game.status == Status.STARTED): + x, y, player1_score, player2_score = game.moveBall() + # Send a message to the game group with the game id, the move coordinates + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.ball", + "game_id": game_id, + "x": x, + "y": y, + "player1_score": player1_score, + "player2_score": player2_score, + } + ) + elif (game.status == Status.ENDED): + await self.end_handler(game_id, game) + + elif action == "paddle": #? Needs validation + # Make a move in a game + game_id = data["game_id"] + dir = data["direction"] + # Move and Get paddle coordinate + game = await GAMES.get(game_id) #? When games status is ended, game_id is deleted from GAMES cache + if (game != None): #? So game becomes None. Is this check enough? or moving delete to end solve without this + y = game.movePaddle(self.user.username, dir) + # Send a message to the game group with the game id, the paddle coordinate, and the player's username + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.paddle", + "game_id": game_id, + "y": y, + "player": self.user.username, + } + ) + ### TOURNAMENT + + elif action == "create": + # Create a new tournament + name = data["name"] + #start_date = data["start_date"] + #end_date = data["end_date"] + # Create a new tournament instance with the given details and save it to the database + tournament = await Tournament.objects.acreate(name) + # Send a message to the 'online' group with the tournament id and the tournament details + await self.channel_layer.group_send( + "online", + { + "type": "tournament.create", + "tournament_id": tournament.id, + "name": tournament.name, + "start_date": tournament.start_date, + #"end_date": tournament.end_date, + } + ) + elif action == "join": + # Join a tournament + tournament_id = data["tournament_id"] + # Get the tournament instance from the database + tournament = await Tournament.objects.aget(id = tournament_id) + # Add the user to the tournament players and save it to the database + tournament.standings.add(self.user) + await tournament.asave() + # Send a message to the 'online' group with the tournament id and the user's username + await self.channel_layer.group_send( + "online", + { + "type": "tournament.join", + "tournament_id": tournament.id, + "username": self.user.username, + } + ) + elif action == "leave": + # Leave a tournament + tournament_id = data["tournament_id"] + # Get the tournament instance from the database + tournament = await Tournament.objects.aget(id = tournament_id) + # Remove the user from the tournament players and save it to the database + tournament.standings.remove(self.user) + await tournament.asave() + # Send a message to the 'online' group with the tournament id and the user's username + await self.channel_layer.group_send( + "online", + { + "type": "tournament.leave", + "tournament_id": tournament.id, + "username": self.user.username, + } + ) + elif action == "cancel": # TODO Only the tournament creator can cancel the tournament + # Cancel a tournament + tournament_id = data["tournament_id"] + # Get the tournament instance from the database + tournament = await Tournament.objects.aget(id = tournament_id) + # Delete the tournament instance from the database + await tournament.adelete() + # Send a message to the 'online' group with the tournament id + await self.channel_layer.group_send( + "online", + { + "type": "tournament.cancel", + "tournament_id": tournament.id, + } + ) + elif action == "start": # TODO Only the tournament creator can start the tournament + # Start a tournament + tournament_id = data["tournament_id"] + # Get the tournament instance from the database + tournament = await Tournament.objects.aget(id = tournament_id) + # Update the tournament status to 'started' and save it to the database + tournament.status = "started" + await tournament.asave() + # Create the first round of matches and save them to the database + matches = await self.create_matches(tournament) + # Send a message to the 'online' group with the tournament id and the matches details + for match in matches: + await self.accept_handler(match.group_name, match.player1.username, match.player2.username, tournament_id) + """ await self.channel_layer.group_send( + "online", + { + "type": "tournament.start", + "tournament_id": tournament.id, + "matches": [ + { + "match_id": match.id, + "player1": match.player1.username, + "player2": match.player2.username, + } + for match in matches + ], + } + ) """ + """ elif action == "finish": # TODO move logic to end_handler + # Finish a match in a tournament + tournament_id = data["tournament_id"] + match_id = data["match_id"] + winner = data["winner"] + # Get the match instance from the database + match = await GAMES.get(match_id) + # Update the match status to 'finished' and save it to the database + match.status = Status.ENDED + await Game.objects.asave(id = match_id) + # Remove loser from the standings and save it to the database + tournament = await Tournament.objects.aget(id = tournament_id) + loser = match.player1.username == winner and match.player2.username or match.player1.username + loser = await UserProfile.objects.aget(username = loser) + tournament.standings.remove(loser) + # Send a message to the 'online' group with the match id and the winner's username + await self.channel_layer.group_send( + "online", + { + "type": "tournament.finish", + "match_id": match.id, + "winner": winner, + } + ) """ + elif action == "next": # TODO Somehow check if all players finished their matches + # Start the next round of matches in a tournament + tournament_id = data["tournament_id"] + # Get the tournament instance from the database + tournament = await Tournament.objects.aget(id = tournament_id) + # Check if the tournament is over + if await tournament.standings.acount() == 1: + # Update the tournament status to 'ended' and save it to the database + tournament.status = "ended" + await tournament.asave() + # Send a message to the 'online' group with the tournament id and the winner's username + await self.channel_layer.group_send( + "online", + { + "type": "tournament.end", + "tournament_id": tournament.id, + "winner": tournament.standings.username, #? Is this correct? + } + ) + else: + # Create the next round of matches and save them to the database + matches = await self.create_matches(tournament) + # Send a message to the 'online' group with the tournament id and the matches details + for match in matches: + await self.accept_handler(match.group_name, match.player1.username, match.player2.username, tournament_id) + """ await self.channel_layer.group_send( + "online", + { + "type": "tournament.next", + "tournament_id": tournament.id, + "matches": [ + { + "match_id": match.id, + "player1": match.player1.username, + "player2": match.player2.username, + } + for match in matches + ], + } + ) """ + + # Handler methods for indivual or tournament games + async def accept_handler(self, group_name, accepted, accepter, tournament_id=None): + # Create a new game instance and save it to the database + game = await self.create_game(group_name, accepted, accepter, tournament_id) + # Create a new game instance and save it to the cache + await GAMES.set(game.id, PongGame(accepted, accepter, tournament_id)) + if tournament_id != None: + await self.channel_layer.group_send( + "online", + { + "type": "tournament.match", + "tournament_id": tournament_id, + "match_id": game.id, + "player1": game.player1.username, + "player2": game.player2.username, + + } + ) + # Send a message to the game group with the game id and the players' usernames + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.accept", + "game_id": game.id, + "accepter": accepter, + "accepted": accepted + } + ) + + async def leave_handler(self, game_id, left, opponent): + # Get scores + game = await GAMES.get(game_id) + left_score = game.getScore(left) # blocking? + opponent_score = MAX_SCORE # set max score automaticaly + # Record the game + await self.record_game(game_id, left_score, opponent_score, opponent) + # Send a message to the game group with the game id and the opponent's username + if game.tournament_id != None: + await self.channel_layer.group_send( + "online", + { + "type": "tournament.match.leave", + "tournament_id": game.tournament_id, + "match_id": game_id, + "left_score": left_score, + "opponent_score": opponent_score, + "winner": opponent, + } + ) + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.leave", + "game_id": game_id, + "left_score": left_score, + "opponent_score": opponent_score, + "winner": opponent, + } + ) + # Discard both from the game group + standing_channel_name = await USER_CHANNEL_NAME.get(opponent) + await self.channel_layer.group_discard(game.group_name, self.channel_name) + await self.channel_layer.group_discard(game.group_name, standing_channel_name) + # Update the game status to 'ended' or delete it + await GAMES.delete(game_id) + + async def end_handler(self, game_id, game): + # Get scores + player1_score = game.player1.score + player2_score = game.player2.score + winner = player1_score > player2_score and game.player1.username or game.player2.username + # Set the game winner, scores and save it to the database + await self.record_game(game_id, player1_score, player2_score, winner) + # Send a message to the game group with the game id and the winner's username + if game.tournament_id != None: + # Remove loser from the standings and save it to the database #TODO + await self.channel_layer.group_send( + "online", + { + "type": "tournament.match.end", + "tournament_id": game.tournament_id, + "match_id": game_id, + "player1_score": player1_score, + "player2_score": player2_score, + "winner": winner, + } + ) + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.end", + "game_id": game_id, + "player1_score": player1_score, #? maybe redundant + "player2_score": player2_score, + "winner": winner, + } + ) + # delete game from cache + await GAMES.delete(game_id) + + # Helper methods to interact with the database + async def create_game(self, group_name, player1, player2, tournament_id=None): + # Create a new game instance with the given players and an group_name + accepted = await UserProfile.objects.aget(username=player1) + accepter = await UserProfile.objects.aget(username=player2) + tournament = await Tournament.objects.aget(id=tournament_id) if tournament_id != None else None + game = await Game.objects.acreate(group_name=group_name, player1=accepted, player2=accepter, tournament=tournament) + return game + + async def record_game(self, game_id, player1_score, player2_score, winner): + game = await Game.objects.aget(id=game_id) + await USER_STATUS.set(game.player1.username, 'online') #? + await USER_STATUS.set(game.player2.username, 'online') #? + game.player1_score = player1_score + game.player2_score = player2_score + game.winner = await UserProfile.objects.aget(username=winner) + await game.asave() + + async def record_for_disconnected(self, game_id, game): + if game.player1.username == self.user.username: + await self.record_game(game_id, game.player1.score, MAX_SCORE, game.player2.username) + else: + await self.record_game(game_id, MAX_SCORE, game.player2.score, game.player1.username) + + async def check_is_user_exist(self, username): + answer = await UserProfile.objects.filter(username=username).aexists() + if not answer: + await self.send(text_data=json.dumps({ + "error": "User doesn't exist", + })) + return answer + + async def check_is_user_online(self, username): + answer = await USER_STATUS.get(username) == 'online' + if not answer: + await self.send(text_data=json.dumps({ + "error": "User is already playing", + })) + return answer + + + @database_sync_to_async + def create_matches(self, tournament): + # Create the next round of matches for the tournament + # This is a simplified logic that assumes the number of players is a power of two + players = list(tournament.standings.all()) + random.shuffle(players) + matches = [] + for i in range(0, len(players), 2): + # Create a new match instance with the pair of players and the tournament + match = Game.objects.create(player1=players[i], player2=players[i+1], tournament=tournament) + matches.append(match) + return matches + + + # Handler methods for different types of messages + async def user_online(self, event): + # Handle a message that a user is online + username = event["username"] + users = event["users"] + # Send a message to the client with the username + await self.send(text_data=json.dumps({ + "type": "user.online", + "username": username, + "users": users, + })) + + async def user_offline(self, event): + # Handle a message that a user is offline + username = event["username"] + #users = event["users"] + # Send a message to the client with the username + await self.send(text_data=json.dumps({ + "type": "user.offline", + "username": username, + })) + + # Receive message from room group + async def chat_message(self, event): + message = event["message"] + user = event["user"] + created_date = event["created_date"] + # Send message to WebSocket + await self.send(text_data=json.dumps({ + "type": "chat.message", + "message": message, + "user": user, + "created_date": created_date, + })) + + async def game_invite(self, event): + # Handle a message that a game is invited + group_name = event["group_name"] + inviter = event["inviter"] + invited = event["invited"] + # Send a message to the client with the game id and the players' usernames + await self.send(text_data=json.dumps({ + "type": "game.invite", + "group_name": group_name, + "inviter": inviter, + "invited": invited, + })) + + async def game_accept(self, event): + # Handle a message that a game is accepted + game_id = event["game_id"] + accepted = event["accepted"] + accepter = event["accepter"] + # Send a message to the client with the game id and the players' usernames + await self.send(text_data=json.dumps({ + "type": "game.accept", + "game_id": game_id, + "accepted": accepted, + "accepter": accepter, + })) + + async def game_decline(self, event): + # Handle a message that a game is declined + decliner = event["decliner"] + declined = event["declined"] + # Send a message to the client with the game id + await self.send(text_data=json.dumps({ + "type": "game.decline", + "declined": declined, + "decliner": decliner, + })) + + async def game_start(self, event): + # Handle a message that a vote is made to start a game + game_id = event["game_id"] + player1 = event["player1"] + player2 = event["player2"] + vote = event["vote"] + # Send a message to the client with the game id and the players' usernames + await self.send(text_data=json.dumps({ + "type": "game.start", + "game_id": game_id, + "player1": player1, + "player2": player2, + "vote": vote, + })) + + async def game_leave(self, event): + # Handle a message that a game is left + game_id = event["game_id"] + left_score = event["left_score"] + opponent_score = event["opponent_score"] + winner = event["winner"] + # Send a message to the client with the game id + await self.send(text_data=json.dumps({ + "type": "game.leave", + "game_id": game_id, + "left_score": left_score, + "opponent_score": opponent_score, + "winner": winner, + })) + + async def game_end(self, event): + # Handle a message that a game is ended + game_id = event["game_id"] + player1_score = event["player1_score"] + player2_score = event["player2_score"] + winner = event["winner"] + # Send a message to the client with the game id and the winner's username + await self.send(text_data=json.dumps({ + "type": "game.end", + "game_id": game_id, + "player1_score": player1_score, + "player2_score": player2_score, + "winner": winner, + })) + + async def game_ball(self, event): + # Handle a message that a ball move is made in a game + game_id = event["game_id"] + x = event["x"] + y = event["y"] + player1_score = event["player1_score"] + player2_score = event["player2_score"] + # Send a message to the client with the game id, the move coordinates, and the player's username + await self.send(text_data=json.dumps({ + "type": "game.ball", + "game_id": game_id, + "x": x, + "y": y, + "player1_score": player1_score, + "player2_score": player2_score, + })) + + async def game_paddle(self, event): + # Handle a message that a paddle move is made in a game + game_id = event["game_id"] + y = event["y"] + player = event["player"] + # Send a message to the client with the game id, the move coordinates, and the player's username + await self.send(text_data=json.dumps({ + "type": "game.paddle", + "game_id": game_id, + "y": y, + "player": player, + })) + +''' + async def tournament_join(self, event): + # Handle a message that a user joined a tournament + tournament_id = event["tournament_id"] + username = event["username"] + # Send a message to the client with the tournament id and the username + await self.send(text_data=json.dumps({ + "type": "tournament.join", + "tournament_id": tournament_id, + "username": username, + })) + + async def tournament_start(self, event): + # Handle a message that a tournament is started + tournament_id = event["tournament_id"] + matches = event["matches"] + # Send a message to the client with the tournament id and the matches details + await self.send(text_data=json.dumps({ + "type": "tournament.start", + "tournament_id": tournament_id, + "matches": matches, + })) + + async def tournament_finish(self, event): + # Handle a message that a match is finished in a tournament + match_id = event["match_id"] + winner = event["winner"] + # Send a message to the client with the match id and the winner's username + await self.send(text_data=json.dumps({ + "type": "tournament.finish", + "match_id": match_id, + "winner": winner, + })) + + async def tournament_next(self, event): + # Handle a message that the next round of matches is started in a tournament + tournament_id = event["tournament_id"] + matches = event["matches"] + # Send a message to the client with the tournament id and the matches details + await self.send(text_data=json.dumps({ + "type": "tournament.next", + "tournament_id": tournament_id, + "matches": matches, + })) + + async def tournament_end(self, event): + # Handle a message that a tournament is ended + tournament_id = event["tournament_id"] + winner = event["winner"] + # Send a message to the client with the tournament id and the winner's username + await self.send(text_data=json.dumps({ + "type": "tournament.end", + "tournament_id": tournament_id, + "winner": winner, + })) +''' diff --git a/indianpong/pong/context_processors.py b/indianpong/pong/context_processors.py new file mode 100644 index 0000000..490d68f --- /dev/null +++ b/indianpong/pong/context_processors.py @@ -0,0 +1,13 @@ +from django.shortcuts import get_object_or_404 +from .models import UserProfile + +def userinfo(request): + if not request.user.is_authenticated: + return {} + """ excluded_paths = ['/login'] #TODO Maybe this is not required + if request.path in excluded_paths: + return {} + """ + profile = get_object_or_404(UserProfile, username=request.user.username) + profile_avatar = profile.avatar.url if profile.avatar else "/static/assets/profile/profilephoto.jpeg" + return {'username': profile.username, 'avatar': profile_avatar} \ No newline at end of file diff --git a/indianpong/pong/forms.py b/indianpong/pong/forms.py index 303be9a..e8b6ce5 100644 --- a/indianpong/pong/forms.py +++ b/indianpong/pong/forms.py @@ -1,124 +1,259 @@ -# forms.py - -from django import forms -from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, UserChangeForm, PasswordChangeForm, PasswordResetForm, SetPasswordForm -from .models import BlockedUser, OAuthToken, UserProfile, Tournament, TournamentMatch - -class UserProfileForm(UserCreationForm): - - username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'input'})) - displayname = forms.CharField(label='Displayname', widget=forms.TextInput(attrs={'class': 'input'})) - email = forms.EmailField(label='Email', widget=forms.EmailInput(attrs={'class': 'input'})) - password1 = forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class': 'input'})) - password2 = forms.CharField(label='RePassword', widget=forms.PasswordInput(attrs={'class': 'input'})) - avatar = forms.ImageField(required=False ,label='Avatar', widget=forms.FileInput(attrs={'class': 'input'})) - class Meta: - model = UserProfile - fields = ['username', 'displayname', 'email', 'password1', 'password2', 'avatar'] - -class AuthenticationUserForm(AuthenticationForm): - username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'input'})) - password = forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class': 'input'})) - class Meta: - model = UserProfile - fields = ['username', 'password'] - -class UpdateUserProfileForm(UserChangeForm): - username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'input'})) - displayname = forms.CharField(label='Displayname', widget=forms.TextInput(attrs={'class': 'input'})) - email = forms.EmailField(label='Email', widget=forms.EmailInput(attrs={'class': 'input'})) - avatar = forms.ImageField(required=False ,label='Avatar', widget=forms.FileInput(attrs={'class': 'input'})) - class Meta: - model = UserProfile - fields = ['username', 'displayname', 'email', 'avatar'] - -""" class UpdateProfileForm(forms.ModelForm): - username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'input'})) - displayname = forms.CharField(label='Displayname', widget=forms.TextInput(attrs={'class': 'input'})) - email = forms.EmailField(label='Email', widget=forms.EmailInput(attrs={'class': 'input'})) - avatar = forms.ImageField(required=False ,label='Avatar', widget=forms.FileInput(attrs={'class': 'input'})) - class Meta: - model = UserProfile - fields = ['username', 'displayname', 'email', 'avatar'] """ - - -class PasswordChangeUserForm(PasswordChangeForm): - old_password = forms.CharField(label='Old Password', widget=forms.PasswordInput(attrs={'class': 'input'})) - new_password1 = forms.CharField(label='New Password', widget=forms.PasswordInput(attrs={'class': 'input'})) - new_password2 = forms.CharField(label='ReNew Password', widget=forms.PasswordInput(attrs={'class': 'input'})) - class Meta: - model = UserProfile - fields = ['old_password', 'new_password1', 'new_password2'] - -class PasswordResetUserForm(PasswordResetForm): - email = forms.EmailField(label='Email', widget=forms.EmailInput(attrs={'class': 'input'})) - class Meta: - model = UserProfile - fields = ['email'] - -#After reset password -class SetPasswordUserForm(SetPasswordForm): - new_password1 = forms.CharField(label='New Password', widget=forms.PasswordInput(attrs={'class': 'input'})) - new_password2 = forms.CharField(label='ReNew Password', widget=forms.PasswordInput(attrs={'class': 'input'})) - uidb64 = forms.CharField(widget=forms.HiddenInput(attrs={'class': 'input'})) - token = forms.CharField(widget=forms.HiddenInput(attrs={'class': 'input'})) - class Meta: - model = UserProfile - fields = ['new_password1', 'new_password2'] - - -class BlockUserForm(forms.ModelForm): - blocked_user = forms.ModelChoiceField(queryset=UserProfile.objects.all(), widget=forms.Select(attrs={'class': 'input'})) - - class Meta: - model = BlockedUser - fields = ['blocked_user'] - -class TournamentForm(forms.ModelForm): - class Meta: - model = Tournament - fields = ['name'] - -class TournamentMatchForm(forms.ModelForm): - class Meta: - model = TournamentMatch - fields = ['tournament', 'player1', 'player2'] - -class OAuthTokenForm(forms.ModelForm): - class Meta: - model = OAuthToken - fields = ['access_token', 'refresh_token', 'expires_at'] - - def savem2m(self): - pass - -""" class InviteToGameForm(forms.ModelForm): - invited_user = forms.ModelChoiceField(queryset=UserProfile.objects.all(), widget=forms.Select(attrs={'class': 'input'})) - - class Meta: - model = GameInvitation - fields = ['message'] - widgets = { - 'message': forms.Textarea(attrs={'rows': 3}), - } -""" - -""" class ChatMessageForm(forms.ModelForm): - class Meta: - model = ChatMessage - fields = ['content'] - widgets = { - 'content': forms.Textarea(attrs={'rows': 3}), - } """ - -""" class TwoFactorAuthSetupForm(forms.ModelForm): - class Meta: - model = TwoFactorAuth - fields = ['is_enabled'] - -class JWTTokenForm(forms.ModelForm): - class Meta: - model = JWTToken - fields = ['token'] """ - - +from datetime import timedelta +from email.mime.image import MIMEImage +import os +from django.core.mail import EmailMultiAlternatives +from django.core.mail import send_mail +from django import forms +from django.urls import reverse +from django.utils.safestring import mark_safe +from django.utils import timezone +from django.contrib.auth.tokens import default_token_generator +from django.template.loader import render_to_string +from django.utils.http import urlsafe_base64_encode +from django.utils.encoding import force_bytes +from indianpong.settings import EMAIL_HOST_USER, STATICFILES_DIRS +from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, UserChangeForm, PasswordChangeForm, PasswordResetForm, SetPasswordForm +from .models import Social, VerifyToken, BlockedUser, ChatMessage, GameInvitation, UserProfile, TwoFactorAuth, JWTToken, Tournament, TournamentMatch + +class UserProfileForm(UserCreationForm): + + username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'input'})) + displayname = forms.CharField(label='Displayname', widget=forms.TextInput(attrs={'class': 'input'})) + email = forms.EmailField(label='Email', widget=forms.EmailInput(attrs={'class': 'input'})) + password1 = forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class': 'input'})) + password2 = forms.CharField(label='RePassword', widget=forms.PasswordInput(attrs={'class': 'input'})) + #avatar = forms.ImageField(required=False ,label='Avatar', widget=forms.FileInput(attrs={'class': 'input'})) + class Meta: + model = UserProfile + fields = ['username', 'displayname', 'email', 'password1', 'password2'] + + """ def confirm_login_allowed(self, user): + if not user.is_verified: + raise forms.ValidationError( + mark_safe( + "This account is not verified. Resend verification email".format( + reverse("password_reset") + ) + ) + ) """ + + def clean_email(self): #TODO not just 42kocaeli.com.tr + email = self.cleaned_data.get('email') + + if "@student.42" in email: + raise forms.ValidationError('Use 42 login') + return email + + def clean_username(self): + username = self.cleaned_data.get("username") + if not username.isascii(): + raise forms.ValidationError("Username cannot contain non-ASCII characters.") + return username + +class StoreItemActionForm(forms.Form): + ACTION_CHOICES = [ + ('buy', 'Buy'), + ('equip', 'Equip'), + ('customize', 'Customize'), + ] + name = forms.CharField(max_length=200, required=False) + action = forms.ChoiceField(choices=ACTION_CHOICES) + whatis = forms.CharField(max_length=200, required=False) + +class SocialForm(forms.ModelForm): + stackoverflow = forms.CharField(label='Stackoverflow', widget=forms.TextInput(attrs={'class': 'form-control'}), required=False) + twitter = forms.CharField(label='Twitter', widget=forms.TextInput(attrs={'class': 'form-control'}), required=False) + instagram = forms.CharField(label='Instagram', widget=forms.TextInput(attrs={'class': 'form-control'}), required=False) + github = forms.CharField(label='Github', widget=forms.TextInput(attrs={'class': 'form-control'}), required=False) + + class Meta: + model = Social + fields = ['stackoverflow', 'twitter', 'instagram', 'github'] + +class DeleteAccountForm(forms.Form): + #password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'}), help_text="Enter your password to confirm account deletion.") + email = forms.EmailField(label='Email', widget=forms.EmailInput(attrs={'class': 'form-control'}), help_text="Enter your email to confirm account deletion.") + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user') + super(DeleteAccountForm, self).__init__(*args, **kwargs) + + def clean(self): + cleaned_data = super().clean() + email = cleaned_data.get('email') + if not self.user.email == email: + raise forms.ValidationError("Email does not match") + return cleaned_data + +class AuthenticationUserForm(AuthenticationForm): + username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'input'})) + password = forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class': 'input'})) + class Meta: + model = UserProfile + fields = ['username', 'password'] + +class UpdateUserProfileForm(UserChangeForm): + username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'form-control'})) + email = forms.EmailField(label='Email', widget=forms.EmailInput(attrs={'class': 'form-control'})) + displayname = forms.CharField(label='Displayname', widget=forms.TextInput(attrs={'class': 'form-control'})) + #password = forms.CharField(widget=forms.HiddenInput(), required=False, help_text="") + password = None + class Meta: + model = UserProfile + fields = ['username', 'email', 'displayname'] + #exclude = ['password'] + +class ProfileAvatarForm(forms.ModelForm): + avatar = forms.ImageField(label='Avatar', widget=forms.FileInput(attrs={'class': 'form-control'})) + class Meta: + model = UserProfile + fields = ['avatar'] + +class PasswordChangeUserForm(PasswordChangeForm): + old_password = forms.CharField(label='Old Password', widget=forms.PasswordInput(attrs={'class': 'form-control'})) + new_password1 = forms.CharField(label='New Password', widget=forms.PasswordInput(attrs={'class': 'form-control'})) + new_password2 = forms.CharField(label='ReNew Password', widget=forms.PasswordInput(attrs={'class': 'form-control'})) + class Meta: + model = UserProfile + fields = ['old_password', 'new_password1', 'new_password2'] + +class PasswordResetUserForm(PasswordResetForm): + #email = forms.EmailField(label='Email', widget=forms.EmailInput(attrs={'class': 'input'})) + + """ class Meta: + model = UserProfile + fields = ['email'] """ + + def save(self, domain_override=None, token_generator=default_token_generator, request=None): + email = self.cleaned_data["email"] + # check if user exists with given email + user = UserProfile.objects.filter(email=email).first() + if user is None: + self.add_error('email', 'User does not exist with this email.') + return + token = token_generator.make_token(user) + VerifyToken.objects.create(user=user, token=token) + mail_subject = 'Reset your password' + message = render_to_string('password_reset_email.html', { + 'user': user, + 'domain': domain_override or request.META['HTTP_HOST'], + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), + 'token': token, + }) + + + email = EmailMultiAlternatives( + subject=mail_subject, + body=message, # this is the simple text version + from_email=EMAIL_HOST_USER, + to=[user.email] + ) + + # Add the HTML version. This could be the same as the body if your email is only HTML. + email.attach_alternative(message, "text/html") + + # List of images + images = ['github.png', '268a.jpg', 'back.png', 'head.png'] + + for img_name in images: + img_path = os.path.join(STATICFILES_DIRS[0], "assets", "email", img_name) + + # Open the image file in binary mode + with open(img_path, 'rb') as f: + image_data = f.read() + + # Create a MIMEImage + img = MIMEImage(image_data) + + # Add a 'Content-ID' header. The angle brackets are important. + img.add_header('Content-ID', f'<{img_name}>') + + # Attach the image to the email + email.attach(img) + + # Send the email + email.send(fail_silently=True) + #send_mail(mail_subject, message, EMAIL_HOST_USER, [user.email], fail_silently=True, html_message=message) + + +""" class TokenValidationForm(forms.Form): + token = forms.CharField(label='Token', widget=forms.TextInput(attrs={'class': 'input'}) ) + + class Meta: + model = Token + fields = ['token'] + + def clean_token(self): + token = self.cleaned_data.get('token') + token_obj = Token.objects.filter(token=token).first() + if not token_obj or timezone.now() - token_obj.created_at > timedelta(minutes=2): + raise forms.ValidationError('Invalid or expired token.') + return token """ + +#After reset password +class SetPasswordUserForm(SetPasswordForm): + new_password1 = forms.CharField(label='New Password', widget=forms.PasswordInput(attrs={'class': 'input'})) + new_password2 = forms.CharField(label='ReNew Password', widget=forms.PasswordInput(attrs={'class': 'input'})) + + class Meta: + model = UserProfile + fields = ['new_password1', 'new_password2'] + + def save(self, commit=True): + user = super().save(commit=False) + if commit: + user.save() + VerifyToken.objects.filter(user=user).delete() + return user + + + + +class ChatMessageForm(forms.ModelForm): + class Meta: + model = ChatMessage + fields = ['content'] + widgets = { + 'content': forms.Textarea(attrs={'rows': 3}), + } + +class BlockUserForm(forms.ModelForm): + blocked_user = forms.ModelChoiceField(queryset=UserProfile.objects.all(), widget=forms.Select(attrs={'class': 'input'})) + + class Meta: + model = BlockedUser + fields = ['blocked_user'] + +class InviteToGameForm(forms.ModelForm): + invited_user = forms.ModelChoiceField(queryset=UserProfile.objects.all(), widget=forms.Select(attrs={'class': 'input'})) + + class Meta: + model = GameInvitation + fields = ['message'] + widgets = { + 'message': forms.Textarea(attrs={'rows': 3}), + } + + +class TwoFactorAuthSetupForm(forms.ModelForm): + class Meta: + model = TwoFactorAuth + fields = ['is_enabled'] + +class JWTTokenForm(forms.ModelForm): + class Meta: + model = JWTToken + fields = ['token'] + + +class TournamentForm(forms.ModelForm): + class Meta: + model = Tournament + fields = ['name'] + +class TournamentMatchForm(forms.ModelForm): + class Meta: + model = TournamentMatch + fields = ['tournament', 'player1', 'player2'] + + diff --git a/indianpong/pong/game.py b/indianpong/pong/game.py index 6ca02ae..1f2a6cf 100644 --- a/indianpong/pong/game.py +++ b/indianpong/pong/game.py @@ -1,112 +1,113 @@ -from enum import Enum -import random - -WIDTH = 800 -HEIGHT = 600 - -PAD_WIDTH = 10 -PAD_HEIGHT = 75 -PAD_SPEED = 50 -PADDLE_Y = (HEIGHT - PAD_HEIGHT) / 2 - -BALL_RADIUS = 10 -BALL_SPEED = 1 - -MAX_SCORE = 20 - -class Status(Enum): - ACCEPTED = 0 - WAITING = 1 - STARTED = 2 - ENDED = 3 - - -class Ball: - def __init__(self): - self.x = WIDTH / 2 - self.y = HEIGHT / 2 - self.radius = BALL_RADIUS - self.speed = BALL_SPEED - self.dx = 1 - self.dy = random.choice([-1, 1]) - -class Paddle: - def __init__(self, x, y, width, height, dy): - self.x = x - self.y = y - self.width = width - self.height = height - self.dy = dy - -# Player class have username and score and Paddle object -class Player: - def __init__(self, username): - self.username = username - self.score = 0 - self.paddle = None - -# Game class have one Ball and two Players objects -class PongGame: - def __init__(self, player1, player2): - self.status = Status.ACCEPTED - self.group_name = player1 + "-" + player2 - self.ball = Ball() - self.player1 = Player(player1) - self.player2 = Player(player2) - self.player1.paddle =Paddle(0, PADDLE_Y, PAD_WIDTH, PAD_HEIGHT, PAD_SPEED) - self.player2.paddle = Paddle(WIDTH - PAD_WIDTH, PADDLE_Y, PAD_WIDTH, PAD_HEIGHT, PAD_SPEED) - - def moveBall(self): - self.ball.x += self.ball.speed * self.ball.dx - self.ball.y += self.ball.speed * self.ball.dy - - # Check for collisions with paddles - if self.ball.y + self.ball.radius > self.player1.paddle.y and self.ball.y - self.ball.radius < self.player1.paddle.y + self.player1.paddle.height and self.ball.dx < 0: - if self.ball.x - self.ball.radius < self.player1.paddle.x + self.player1.paddle.width: - self.ball.dx *= -1 - elif self.ball.y + self.ball.radius > self.player2.paddle.y and self.ball.y - self.ball.radius < self.player2.paddle.y + self.player2.paddle.height and self.ball.dx > 0: - if self.ball.x + self.ball.radius > self.player2.paddle.x: - self.ball.dx *= -1 - # Check for collisions with top/bottom walls - if self.ball.y + self.ball.radius > HEIGHT or self.ball.y - self.ball.radius < 0: - self.ball.dy *= -1 - # Check for collisions with left/right walls (scoring) - if self.ball.x + self.ball.radius > WIDTH: - self.player1.score += 1 - self.resetBall() - elif self.ball.x - self.ball.radius < 0: - self.player2.score += 1 - self.resetBall() - return self.ball.x, self.ball.y, self.player1.score, self.player2.score - - - def movePaddle(self, player, direction): - # Move the paddles - mover = player == self.player1.username and self.player1 or self.player2 - if direction == "up": - mover.paddle.y -= mover.paddle.dy - if mover.paddle.y < 0: - mover.paddle.y = 0 - elif direction == "down": - mover.paddle.y += mover.paddle.dy - if mover.paddle.y > HEIGHT - mover.paddle.height: - mover.paddle.y = HEIGHT - mover.paddle.height - return mover.paddle.y - - def resetBall(self): - self.ball.x = WIDTH / 2 - self.ball.y = HEIGHT / 2 - self.ball.dx *= -1 - self.ball.dy = random.choice([-1, 1]) - if self.player1.score >= MAX_SCORE or self.player2.score >= MAX_SCORE: - self.status = Status.ENDED - - def getScore(self, username): - if username == self.player1.username: - return self.player1.score - elif username == self.player2.username: - return self.player2.score - else: - return None - - +from enum import Enum +import random + +WIDTH = 800 +HEIGHT = 600 + +PAD_WIDTH = 10 +PAD_HEIGHT = 75 +PAD_SPEED = 50 +PADDLE_Y = (HEIGHT - PAD_HEIGHT) / 2 + +BALL_RADIUS = 10 +BALL_SPEED = 1 + +MAX_SCORE = 20 + +class Status(Enum): + ACCEPTED = 0 + WAITING = 1 + STARTED = 2 + ENDED = 3 + + +class Ball: + def __init__(self): + self.x = WIDTH / 2 + self.y = HEIGHT / 2 + self.radius = BALL_RADIUS + self.speed = BALL_SPEED + self.dx = 1 + self.dy = random.choice([-1, 1]) + +class Paddle: + def __init__(self, x, y, width, height, dy): + self.x = x + self.y = y + self.width = width + self.height = height + self.dy = dy + +# Player class have username and score and Paddle object +class Player: + def __init__(self, username): + self.username = username + self.score = 0 + self.paddle = None + +# Game class have one Ball and two Players objects +class PongGame: + def __init__(self, player1, player2, tournament_id=None): + self.status = Status.ACCEPTED + self.group_name = player1 + "-" + player2 + self.tournament_id = tournament_id + self.ball = Ball() + self.player1 = Player(player1) + self.player2 = Player(player2) + self.player1.paddle =Paddle(0, PADDLE_Y, PAD_WIDTH, PAD_HEIGHT, PAD_SPEED) + self.player2.paddle = Paddle(WIDTH - PAD_WIDTH, PADDLE_Y, PAD_WIDTH, PAD_HEIGHT, PAD_SPEED) + + def moveBall(self): + self.ball.x += self.ball.speed * self.ball.dx + self.ball.y += self.ball.speed * self.ball.dy + + # Check for collisions with paddles + if self.ball.y + self.ball.radius > self.player1.paddle.y and self.ball.y - self.ball.radius < self.player1.paddle.y + self.player1.paddle.height and self.ball.dx < 0: + if self.ball.x - self.ball.radius < self.player1.paddle.x + self.player1.paddle.width: + self.ball.dx *= -1 + elif self.ball.y + self.ball.radius > self.player2.paddle.y and self.ball.y - self.ball.radius < self.player2.paddle.y + self.player2.paddle.height and self.ball.dx > 0: + if self.ball.x + self.ball.radius > self.player2.paddle.x: + self.ball.dx *= -1 + # Check for collisions with top/bottom walls + if self.ball.y + self.ball.radius > HEIGHT or self.ball.y - self.ball.radius < 0: + self.ball.dy *= -1 + # Check for collisions with left/right walls (scoring) + if self.ball.x + self.ball.radius > WIDTH: + self.player1.score += 1 + self.resetBall() + elif self.ball.x - self.ball.radius < 0: + self.player2.score += 1 + self.resetBall() + return self.ball.x, self.ball.y, self.player1.score, self.player2.score + + + def movePaddle(self, player, direction): + # Move the paddles + mover = player == self.player1.username and self.player1 or self.player2 + if direction == "up": + mover.paddle.y -= mover.paddle.dy + if mover.paddle.y < 0: + mover.paddle.y = 0 + elif direction == "down": + mover.paddle.y += mover.paddle.dy + if mover.paddle.y > HEIGHT - mover.paddle.height: + mover.paddle.y = HEIGHT - mover.paddle.height + return mover.paddle.y + + def resetBall(self): + self.ball.x = WIDTH / 2 + self.ball.y = HEIGHT / 2 + self.ball.dx *= -1 + self.ball.dy = random.choice([-1, 1]) + if self.player1.score >= MAX_SCORE or self.player2.score >= MAX_SCORE: + self.status = Status.ENDED + + def getScore(self, username): + if username == self.player1.username: + return self.player1.score + elif username == self.player2.username: + return self.player2.score + else: + return None + + diff --git a/indianpong/pong/management/commands/indianai.py b/indianpong/pong/management/commands/indianai.py new file mode 100644 index 0000000..9b68fc4 --- /dev/null +++ b/indianpong/pong/management/commands/indianai.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand +from pong.models import UserProfile + +class Command(BaseCommand): + def handle(self, *args, **options): + if not UserProfile.objects.filter(username="IndianAI").exists(): + user = UserProfile.objects.create_user(username="IndianAI", email="indianpong@gmail.com") + user.set_unusable_password() + user.displayname = "Sitting AI" + user.avatar = "indianai_207d5c7.jpg" + user.is_verified = True + user.indian_wallet = 1000 + user.elo_point = 1000 + user.save() \ No newline at end of file diff --git a/indianpong/pong/management/commands/superuser.py b/indianpong/pong/management/commands/superuser.py new file mode 100644 index 0000000..2133e17 --- /dev/null +++ b/indianpong/pong/management/commands/superuser.py @@ -0,0 +1,8 @@ +from django.core.management.base import BaseCommand +from pong.models import UserProfile +from os import environ + +class Command(BaseCommand): + def handle(self, *args, **options): + if not UserProfile.objects.filter(username="Bitlis").exists(): + UserProfile.objects.create_superuser("Bitlis", "bit@g.com", environ.get("SUPER_PASS", default="9247")) \ No newline at end of file diff --git a/indianpong/pong/migrations/0001_initial.py b/indianpong/pong/migrations/0001_initial.py index 359e357..abbe699 100644 --- a/indianpong/pong/migrations/0001_initial.py +++ b/indianpong/pong/migrations/0001_initial.py @@ -1,146 +1,241 @@ -# Generated by Django 5.0 on 2023-12-21 15:37 - -import django.contrib.auth.models -import django.contrib.auth.validators -import django.db.models.deletion -import django.utils.timezone -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='UserProfile', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('displayname', models.CharField(blank=True, max_length=100, null=True)), - ('avatar', models.ImageField(blank=True, null=True, upload_to='avatars/')), - ('wins', models.IntegerField(default=0)), - ('losses', models.IntegerField(default=0)), - ('friends', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - migrations.CreateModel( - name='BlockedUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('blocked_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocked_by_users', to=settings.AUTH_USER_MODEL)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocking_users', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='ChatMessage', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField()), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL)), - ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='GameInvitation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('message', models.TextField()), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('invited_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations_received', to=settings.AUTH_USER_MODEL)), - ('inviting_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations_sent', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='GameWarning', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('opponent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='warnings_received', to=settings.AUTH_USER_MODEL)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='warnings_sent', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='JWTToken', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('token', models.CharField(max_length=255)), - ('expires_at', models.DateTimeField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='MatchHistory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateTimeField(auto_now_add=True)), - ('result', models.CharField(max_length=10)), - ('opponent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opponent_matches', to=settings.AUTH_USER_MODEL)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matches', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='OAuthToken', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('access_token', models.CharField(max_length=255)), - ('refresh_token', models.CharField(max_length=255)), - ('expires_at', models.DateTimeField(blank=True, null=True)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='Tournament', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('start_date', models.DateTimeField()), - ('end_date', models.DateTimeField()), - ('participants', models.ManyToManyField(related_name='tournaments', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='TournamentMatch', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('player1', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tournament_matches_as_player1', to=settings.AUTH_USER_MODEL)), - ('player2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tournament_matches_as_player2', to=settings.AUTH_USER_MODEL)), - ('tournament', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matches', to='pong.tournament')), - ('winner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tournament_wins', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='TwoFactorAuth', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('secret_key', models.CharField(max_length=16)), - ('is_enabled', models.BooleanField(default=False)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] +# Generated by Django 5.0 on 2024-02-14 23:10 + +import datetime +import django.contrib.auth.models +import django.contrib.auth.validators +import django.core.validators +import django.db.models.deletion +import django.utils.timezone +import pong.utils +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='Social', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('stackoverflow', models.CharField(blank=True, max_length=200, null=True)), + ('github', models.CharField(blank=True, max_length=200, null=True)), + ('twitter', models.CharField(blank=True, max_length=200, null=True)), + ('instagram', models.CharField(blank=True, max_length=200, null=True)), + ], + ), + migrations.CreateModel( + name='StoreItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category_name', models.CharField(default='', max_length=100)), + ('name', models.CharField(max_length=100)), + ('image_url', models.TextField()), + ('description', models.TextField()), + ('price', models.IntegerField()), + ('show_status', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='UserGameStat', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('total_games_pong', models.IntegerField(default=0)), + ('total_win_pong', models.IntegerField(default=0)), + ('total_lose_pong', models.IntegerField(default=0)), + ('total_win_streak_pong', models.IntegerField(default=0)), + ('total_lose_streak_pong', models.IntegerField(default=0)), + ('total_win_rate_pong', models.FloatField(default=0.0)), + ('total_avg_game_duration', models.DurationField(blank=True, default=datetime.timedelta(0), null=True)), + ('total_avg_points_won', models.FloatField(default=0.0)), + ('total_avg_points_lost', models.FloatField(default=0.0)), + ], + ), + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('displayname', models.CharField(blank=True, max_length=100, null=True)), + ('email', models.EmailField(max_length=254, unique=True)), + ('avatar', models.ImageField(blank=True, null=True, upload_to=pong.utils.get_upload_to)), + ('is_verified', models.BooleanField(default=False)), + ('is_42student', models.BooleanField(default=False)), + ('indian_wallet', models.IntegerField(blank=True, default=0, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(9999)])), + ('elo_point', models.IntegerField(blank=True, default=0, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(99999)])), + ('friends', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ('social', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pong.social')), + ('game_stats', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pong.usergamestat')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='BlockedUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('blocked_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocked_by_users', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocking_users', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='ChatMessage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField()), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL)), + ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Game', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('group_name', models.CharField(max_length=100)), + ('player1_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(20)])), + ('player2_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(20)])), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('game_duration', models.DurationField(blank=True, null=True)), + ('loser', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='games_lost', to=settings.AUTH_USER_MODEL)), + ('player1', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='games_as_player1', to=settings.AUTH_USER_MODEL)), + ('player2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='games_as_player2', to=settings.AUTH_USER_MODEL)), + ('winner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='games_won', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='GameInvitation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('message', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('invited_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations_received', to=settings.AUTH_USER_MODEL)), + ('inviting_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations_sent', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='GameWarning', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('opponent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='warnings_received', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='warnings_sent', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='JWTToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('token', models.CharField(max_length=255)), + ('expires_at', models.DateTimeField()), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='OAuthToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('access_token', models.CharField(max_length=255)), + ('refresh_token', models.CharField(max_length=255)), + ('expires_in', models.IntegerField(blank=True, null=True)), + ('created_at', models.IntegerField(blank=True, null=True)), + ('secret_valid_until', models.IntegerField(blank=True, null=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Room', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('first_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='room_first', to=settings.AUTH_USER_MODEL)), + ('second_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='room_second', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField(verbose_name='Text Content')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='message_user', to=settings.AUTH_USER_MODEL)), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='message_room', to='pong.room')), + ], + ), + migrations.CreateModel( + name='Tournament', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('status', models.CharField(choices=[('open', 'Opened'), ('started', 'Started'), ('ended', 'Ended')], default='open', max_length=10)), + ('start_date', models.DateTimeField(auto_now_add=True)), + ('participants', models.ManyToManyField(related_name='tournaments', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='TournamentMatch', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('player1', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tournament_matches_as_player1', to=settings.AUTH_USER_MODEL)), + ('player2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tournament_matches_as_player2', to=settings.AUTH_USER_MODEL)), + ('tournament', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matches', to='pong.tournament')), + ('winner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tournament_wins', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='TwoFactorAuth', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('secret_key', models.CharField(max_length=16)), + ('is_enabled', models.BooleanField(default=False)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='UserItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_bought', models.BooleanField(default=False)), + ('is_equipped', models.BooleanField(default=False)), + ('whatis', models.CharField(blank=True, max_length=100, null=True)), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pong.storeitem')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='userprofile', + name='store_items', + field=models.ManyToManyField(blank=True, through='pong.UserItem', to='pong.storeitem'), + ), + migrations.CreateModel( + name='VerifyToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('token', models.CharField(max_length=255)), + ('created_at', models.DateTimeField(default=django.utils.timezone.now)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/indianpong/pong/models.py b/indianpong/pong/models.py index 74bfce1..cee4df0 100644 --- a/indianpong/pong/models.py +++ b/indianpong/pong/models.py @@ -1,133 +1,259 @@ -from django.db import models -from django.contrib.auth.models import AbstractUser -from django.core.validators import MinValueValidator, MaxValueValidator -from django.utils.html import mark_safe -import uuid - -class UserProfile(AbstractUser): - displayname = models.CharField(max_length=100, blank=True, null=True) - avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) - friends = models.ManyToManyField('self', symmetrical=False) - #channel_name = models.CharField(max_length=100, blank=True, null=True) - wins = models.IntegerField(default=0) - losses = models.IntegerField(default=0) - - def __str__(self) -> str: - return f"{self.username}" - - @property - def thumbnail(self): - if self.avatar: - return mark_safe('' % (self.avatar.url)) - else: - return mark_safe('') - -class BlockedUser(models.Model): - user = models.ForeignKey(UserProfile, related_name='blocking_users', on_delete=models.CASCADE) - blocked_user = models.ForeignKey(UserProfile, related_name='blocked_by_users', on_delete=models.CASCADE) - -#------------------------------------------------------------# - -class Room(models.Model): - id = models.UUIDField(primary_key = True, default = uuid.uuid4) - first_user = models.ForeignKey(UserProfile, related_name = "room_first", on_delete = models.CASCADE, null = True) - second_user = models.ForeignKey(UserProfile, related_name = "room_second", on_delete = models.CASCADE, null = True) - - -class Message(models.Model): - user = models.ForeignKey(UserProfile, related_name = "message_user", on_delete = models.CASCADE) - room = models.ForeignKey(Room, related_name = "message_room", on_delete = models.CASCADE) - content = models.TextField(verbose_name = "Text Content") - created_date = models.DateTimeField(auto_now_add = True) - - def get_short_date(self): - return str(self.created_date.strftime("%H:%M")) - -class Game(models.Model): - - group_name = models.CharField(max_length=100) - player1 = models.ForeignKey(UserProfile, related_name='games_as_player1', on_delete=models.CASCADE) - player2 = models.ForeignKey(UserProfile, related_name='games_as_player2', on_delete=models.CASCADE) - player1_score = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(20)], default=0) - player2_score = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(20)], default=0) - created_at = models.DateTimeField(auto_now_add=True) - winner = models.ForeignKey(UserProfile, related_name='games_won', on_delete=models.CASCADE, null=True, blank=True) - - def __str__(self): - return f"{self.player1.username} vs {self.player2.username}" - -class MatchRecord(models.Model): - user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='matches') - opponent = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='opponent_matches') - date = models.DateTimeField(auto_now_add=True) - result = models.CharField(max_length=10) # 'win' or 'lose' - - def __str__(self) -> str: - return f"{self.user + '-' + self.opponent}" - -class Tournament(models.Model): - STATUS_CHOICES = ( - ("open", "Opened"), - ("started", "Started"), - ("ended", "Ended") - ) - - name = models.CharField(max_length=100) - status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="open") - start_date = models.DateTimeField(auto_now_add=True) - #end_date = models.DateTimeField() - participants = models.ManyToManyField(UserProfile, related_name='tournaments') - - def __str__(self) -> str: - return f"{self.name}" - -class TournamentMatch(models.Model): - tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE, related_name='matches') - player1 = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='tournament_matches_as_player1') - player2 = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='tournament_matches_as_player2') - winner = models.ForeignKey(UserProfile, on_delete=models.CASCADE, null=True, blank=True, related_name='tournament_wins') - -class OAuthToken(models.Model): - user = models.OneToOneField(UserProfile, on_delete=models.CASCADE) - access_token = models.CharField(max_length=255) - refresh_token = models.CharField(max_length=255) - expires_at = models.DateTimeField(default=None ,null=True, blank=True) - -""" class ChatMessage(models.Model): - sender = models.ForeignKey(UserProfile, related_name='sent_messages', on_delete=models.CASCADE) - receiver = models.ForeignKey(UserProfile, related_name='received_messages', on_delete=models.CASCADE) - content = models.TextField() - timestamp = models.DateTimeField(auto_now_add=True) """ - -""" class GameInvitation(models.Model): - inviting_user = models.ForeignKey(UserProfile, related_name='invitations_sent', on_delete=models.CASCADE) - invited_user = models.ForeignKey(UserProfile, related_name='invitations_received', on_delete=models.CASCADE) - message = models.TextField() - created_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return f"{self.inviting_user.username} invited {self.invited_user.username} to play Pong" """ - -""" class GameWarning(models.Model): - user = models.ForeignKey(UserProfile, related_name='warnings_sent', on_delete=models.CASCADE) - opponent = models.ForeignKey(UserProfile, related_name='warnings_received', on_delete=models.CASCADE) - created_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return f"{self.user.username} sent a game warning to {self.opponent.username}" """ - - - - - -""" class TwoFactorAuth(models.Model): - user = models.OneToOneField(UserProfile, on_delete=models.CASCADE) - secret_key = models.CharField(max_length=16) # Store the secret key securely - is_enabled = models.BooleanField(default=False) - -class JWTToken(models.Model): - user = models.ForeignKey(UserProfile, on_delete=models.CASCADE) - token = models.CharField(max_length=255) - expires_at = models.DateTimeField() - """ - +from email.mime.image import MIMEImage +import os +from django.db import models +from django.contrib.auth.models import AbstractUser +from django.core.validators import MinValueValidator, MaxValueValidator +from django.utils.html import mark_safe +from django.core.mail import send_mail +from django.template.loader import render_to_string +from django.core.mail import EmailMultiAlternatives +from .utils import get_upload_to +from indianpong.settings import EMAIL_HOST_USER, STATICFILES_DIRS +from django.utils import timezone +import uuid +from datetime import timedelta + +class Social(models.Model): + stackoverflow = models.CharField(max_length=200, blank=True, null=True) + github = models.CharField(max_length=200, blank=True, null=True) + twitter = models.CharField(max_length=200, blank=True, null=True) + instagram = models.CharField(max_length=200, blank=True, null=True) + +class StoreItem(models.Model): + category_name = models.CharField(max_length=100, default="") + name = models.CharField(max_length=100) + image_url = models.TextField() + description = models.TextField() + price = models.IntegerField() + show_status = models.BooleanField(default=False) # store'da görünebilir mi? + +class UserProfile(AbstractUser): + #id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + displayname = models.CharField(max_length=100, blank=True, null=True) + email = models.EmailField(unique=True, max_length=254) + avatar = models.ImageField(upload_to=get_upload_to, null=True, blank=True) + friends = models.ManyToManyField('self', symmetrical=False, blank=True) + social = models.OneToOneField('Social', on_delete=models.SET_NULL, null=True, blank=True) + #channel_name = models.CharField(max_length=100, blank=True, null=True) + is_verified = models.BooleanField(default=False) + is_42student = models.BooleanField(default=False) + store_items = models.ManyToManyField(StoreItem, through='UserItem', blank=True) + game_stats = models.OneToOneField('UserGameStat', on_delete=models.SET_NULL, null=True, blank=True) + indian_wallet = models.IntegerField(blank=True, null=True, default=0, validators=[MinValueValidator(0), MaxValueValidator(9999)]) + elo_point = models.IntegerField(blank=True, null=True, default=0, validators=[MinValueValidator(0), MaxValueValidator(99999)]) + + + def __str__(self) -> str: + return f"{self.username}" + + @property + def thumbnail(self): + if self.avatar: + return mark_safe('' % (self.avatar.url)) + else: + return mark_safe('') + +class UserItem(models.Model): + user = models.ForeignKey(UserProfile, on_delete=models.CASCADE) + item = models.ForeignKey(StoreItem, on_delete=models.CASCADE) + is_bought = models.BooleanField(default=False) # satın alındı mı? + is_equipped = models.BooleanField(default=False) # kullanıma alındı mı veya inventorye eklendi mi? + whatis = models.CharField(max_length=100, blank=True, null=True) # ai name or colors + +class UserGameStat(models.Model): + total_games_pong = models.IntegerField(default=0) + total_win_pong = models.IntegerField(default=0) + total_lose_pong = models.IntegerField(default=0) + total_win_streak_pong = models.IntegerField(default=0) + total_lose_streak_pong = models.IntegerField(default=0) + total_win_rate_pong = models.FloatField(default=0.0) + total_avg_game_duration = models.DurationField(default=timedelta(0), null=True, blank=True) + total_avg_points_won = models.FloatField(default=0.0) + total_avg_points_lost = models.FloatField(default=0.0) + + def formatted_game_duration(self): + if self.total_avg_game_duration is None: + return None + + total_seconds = int(self.total_avg_game_duration.total_seconds()) + hours, remainder = divmod(total_seconds, 3600) + minutes, seconds = divmod(remainder, 60) + + # Format the duration as "1h 3m 2s", "3m 2s", "2s", etc. + if hours: + return f"{hours}h {minutes}m {seconds}s" + elif minutes: + return f"{minutes}m {seconds}s" + else: + return f"{seconds}s" + + def formatted_win_rate(self): + # Win rate'i yüzde cinsinden hesapla ve float olarak döndür + win_rate_percentage = self.total_win_rate_pong * 100 + + # Win rate'i string olarak formatla + return f"%{win_rate_percentage:.1f}" + +class VerifyToken(models.Model): + user = models.ForeignKey(UserProfile, on_delete=models.CASCADE) + token = models.CharField(max_length=255) + created_at = models.DateTimeField(default=timezone.now) + + def send_verification_email(self, request, user): + token = VerifyToken.objects.get(user=user) + mail_subject = 'Activate your account.' + message = render_to_string('activate_account_email.html', { + 'user': user, + 'domain': request.META['HTTP_HOST'], + 'token': token.token, + }) + + email = EmailMultiAlternatives( + subject=mail_subject, + body=message, # this is the simple text version + from_email=EMAIL_HOST_USER, + to=[user.email] + ) + + # Add the HTML version. This could be the same as the body if your email is only HTML. + email.attach_alternative(message, "text/html") + + # List of images + images = ['github.png', '268a.jpg', 'back.png', 'head.png'] + + for img_name in images: + img_path = os.path.join(STATICFILES_DIRS[0], "assets", "email", img_name) + + # Open the image file in binary mode + with open(img_path, 'rb') as f: + image_data = f.read() + + # Create a MIMEImage + img = MIMEImage(image_data) + + # Add a 'Content-ID' header. The angle brackets are important. + img.add_header('Content-ID', f'<{img_name}>') + + # Attach the image to the email + email.attach(img) + + # Send the email + email.send(fail_silently=True) + #send_mail(mail_subject, message, EMAIL_HOST_USER, [user.email], fail_silently=True, html_message=message) + +class ChatMessage(models.Model): + sender = models.ForeignKey(UserProfile, related_name='sent_messages', on_delete=models.CASCADE) + receiver = models.ForeignKey(UserProfile, related_name='received_messages', on_delete=models.CASCADE) + content = models.TextField() + timestamp = models.DateTimeField(auto_now_add=True) + +class BlockedUser(models.Model): + user = models.ForeignKey(UserProfile, related_name='blocking_users', on_delete=models.CASCADE) + blocked_user = models.ForeignKey(UserProfile, related_name='blocked_by_users', on_delete=models.CASCADE) + +class GameInvitation(models.Model): + inviting_user = models.ForeignKey(UserProfile, related_name='invitations_sent', on_delete=models.CASCADE) + invited_user = models.ForeignKey(UserProfile, related_name='invitations_received', on_delete=models.CASCADE) + message = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.inviting_user.username} invited {self.invited_user.username} to play Pong" + +class GameWarning(models.Model): + user = models.ForeignKey(UserProfile, related_name='warnings_sent', on_delete=models.CASCADE) + opponent = models.ForeignKey(UserProfile, related_name='warnings_received', on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.user.username} sent a game warning to {self.opponent.username}" + + +class Game(models.Model): + + group_name = models.CharField(max_length=100) + player1 = models.ForeignKey(UserProfile, related_name='games_as_player1', on_delete=models.CASCADE) + player2 = models.ForeignKey(UserProfile, related_name='games_as_player2', on_delete=models.CASCADE) + player1_score = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(20)], default=0) + player2_score = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(20)], default=0) + created_at = models.DateTimeField(auto_now_add=True) + game_duration = models.DurationField(null=True, blank=True) + winner = models.ForeignKey(UserProfile, related_name='games_won', on_delete=models.CASCADE, null=True, blank=True) + loser = models.ForeignKey(UserProfile, related_name='games_lost', on_delete=models.CASCADE, null=True, blank=True) + + def __str__(self): + return f"{self.player1.username} vs {self.player2.username}" + + def formatted_game_duration(self): + if self.game_duration is None: + return None + + total_seconds = int(self.game_duration.total_seconds()) + hours, remainder = divmod(total_seconds, 3600) + minutes, seconds = divmod(remainder, 60) + + # Format the duration as "1h 3m 2s", "3m 2s", "2s", etc. + if hours: + return f"{hours}h {minutes}m {seconds}s" + elif minutes: + return f"{minutes}m {seconds}s" + else: + return f"{seconds}s" + +class Tournament(models.Model): + STATUS_CHOICES = ( + ("open", "Opened"), + ("started", "Started"), + ("ended", "Ended") + ) + + name = models.CharField(max_length=100) + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="open") + start_date = models.DateTimeField(auto_now_add=True) + #end_date = models.DateTimeField() + participants = models.ManyToManyField(UserProfile, related_name='tournaments') + + def __str__(self) -> str: + return f"{self.name}" + +class TournamentMatch(models.Model): + tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE, related_name='matches') + player1 = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='tournament_matches_as_player1') + player2 = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='tournament_matches_as_player2') + winner = models.ForeignKey(UserProfile, on_delete=models.CASCADE, null=True, blank=True, related_name='tournament_wins') + +class OAuthToken(models.Model): + user = models.OneToOneField(UserProfile, on_delete=models.CASCADE) + access_token = models.CharField(max_length=255) + refresh_token = models.CharField(max_length=255) + expires_in = models.IntegerField(null=True, blank=True) + created_at = models.IntegerField(null=True, blank=True) + secret_valid_until = models.IntegerField(null=True, blank=True) + +class TwoFactorAuth(models.Model): + user = models.OneToOneField(UserProfile, on_delete=models.CASCADE) + secret_key = models.CharField(max_length=16) # Store the secret key securely + is_enabled = models.BooleanField(default=False) + +class JWTToken(models.Model): + user = models.ForeignKey(UserProfile, on_delete=models.CASCADE) + token = models.CharField(max_length=255) + expires_at = models.DateTimeField() + + +#------------------------------------------------------------# + +class Room(models.Model): + id = models.UUIDField(primary_key = True, default = uuid.uuid4) + first_user = models.ForeignKey(UserProfile, related_name = "room_first", on_delete = models.CASCADE, null = True) + second_user = models.ForeignKey(UserProfile, related_name = "room_second", on_delete = models.CASCADE, null = True) + + +class Message(models.Model): + user = models.ForeignKey(UserProfile, related_name = "message_user", on_delete = models.CASCADE) + room = models.ForeignKey(Room, related_name = "message_room", on_delete = models.CASCADE) + content = models.TextField(verbose_name = "Text Content") + created_date = models.DateTimeField(auto_now_add = True) + + def get_short_date(self): + return str(self.created_date.strftime("%H:%M")) diff --git a/indianpong/pong/templates/404.html b/indianpong/pong/templates/404.html index 4d87273..d16c877 100644 --- a/indianpong/pong/templates/404.html +++ b/indianpong/pong/templates/404.html @@ -1,11 +1,11 @@ - -{% extends "base.html" %} - -{% block title %}Not Found{% endblock %} - -{% block app %} -
-
404 ERROR
-
Probably you lost in our website!
-
+ +{% extends "base.html" %} + +{% block title %}Not Found{% endblock %} + +{% block app %} +
+
404 ERROR
+
Probably you lost in our website!
+
{% endblock app %} \ No newline at end of file diff --git a/indianpong/pong/templates/_nav.html b/indianpong/pong/templates/_nav.html index 306afff..b8f3752 100644 --- a/indianpong/pong/templates/_nav.html +++ b/indianpong/pong/templates/_nav.html @@ -1,38 +1,42 @@ -{% load static %} - -