diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72dc365 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv/ +__init__.py \ No newline at end of file diff --git a/NIP_APP/asgi.py b/NIP_APP/asgi.py new file mode 100644 index 0000000..69183e1 --- /dev/null +++ b/NIP_APP/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for NIP_APP project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NIP_APP.settings') + +application = get_asgi_application() diff --git a/NIP_APP/settings.py b/NIP_APP/settings.py new file mode 100644 index 0000000..b0bf5c2 --- /dev/null +++ b/NIP_APP/settings.py @@ -0,0 +1,128 @@ +""" +Django settings for NIP_APP project. + +Generated by 'django-admin startproject' using Django 4.2.23. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +from pathlib import Path + +# 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/4.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-kk922joxhefnfkzxvg_aqecsq546)ae8o22#33+6h))_3^nm#!' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +STATIC_URL = '/static/' + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + # CUSTOMS + 'app', +] + +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 = 'NIP_APP.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 = 'NIP_APP.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.2/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/4.2/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/4.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/NIP_APP/urls.py b/NIP_APP/urls.py new file mode 100644 index 0000000..a18eaed --- /dev/null +++ b/NIP_APP/urls.py @@ -0,0 +1,23 @@ +""" +URL configuration for NIP_APP project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.2/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.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('app.urls')), # APP-URLs Einbindung +] diff --git a/NIP_APP/wsgi.py b/NIP_APP/wsgi.py new file mode 100644 index 0000000..16326dd --- /dev/null +++ b/NIP_APP/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for NIP_APP project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NIP_APP.settings') + +application = get_wsgi_application() diff --git a/app/admin.py b/app/admin.py new file mode 100644 index 0000000..6954e90 --- /dev/null +++ b/app/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from app.models import games, Player, answer + +admin.site.register(games) +admin.site.register(Player) +admin.site.register(answer) diff --git a/app/apps.py b/app/apps.py new file mode 100644 index 0000000..ed327d2 --- /dev/null +++ b/app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'app' diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py new file mode 100644 index 0000000..d2f7a63 --- /dev/null +++ b/app/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.23 on 2025-06-18 23:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='games', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(max_length=14, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('state', models.BooleanField(default=True)), + ], + ), + ] diff --git a/app/migrations/0002_player.py b/app/migrations/0002_player.py new file mode 100644 index 0000000..14e3d21 --- /dev/null +++ b/app/migrations/0002_player.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.23 on 2025-06-18 23:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Player', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='players', to='app.games')), + ], + ), + ] diff --git a/app/migrations/0003_player_leader.py b/app/migrations/0003_player_leader.py new file mode 100644 index 0000000..a27da3b --- /dev/null +++ b/app/migrations/0003_player_leader.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.23 on 2025-06-18 23:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0002_player'), + ] + + operations = [ + migrations.AddField( + model_name='player', + name='leader', + field=models.BooleanField(default=False), + ), + ] diff --git a/app/migrations/0004_answer.py b/app/migrations/0004_answer.py new file mode 100644 index 0000000..df6ad75 --- /dev/null +++ b/app/migrations/0004_answer.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.23 on 2025-06-19 00:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0003_player_leader'), + ] + + operations = [ + migrations.CreateModel( + name='answer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('answer', models.CharField(max_length=250, unique=True)), + ('player', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='app.player')), + ], + ), + ] diff --git a/app/migrations/0005_games_round_open.py b/app/migrations/0005_games_round_open.py new file mode 100644 index 0000000..9d9be4e --- /dev/null +++ b/app/migrations/0005_games_round_open.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.23 on 2025-06-19 00:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0004_answer'), + ] + + operations = [ + migrations.AddField( + model_name='games', + name='round_open', + field=models.BooleanField(default=False), + ), + ] diff --git a/app/migrations/0006_answer_akey.py b/app/migrations/0006_answer_akey.py new file mode 100644 index 0000000..0e8264b --- /dev/null +++ b/app/migrations/0006_answer_akey.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.23 on 2025-06-19 00:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0005_games_round_open'), + ] + + operations = [ + migrations.AddField( + model_name='answer', + name='akey', + field=models.CharField(max_length=1, null=True), + ), + ] diff --git a/app/migrations/0007_alter_answer_answer.py b/app/migrations/0007_alter_answer_answer.py new file mode 100644 index 0000000..41c19bd --- /dev/null +++ b/app/migrations/0007_alter_answer_answer.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.23 on 2025-06-19 01:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0006_answer_akey'), + ] + + operations = [ + migrations.AlterField( + model_name='answer', + name='answer', + field=models.CharField(max_length=250), + ), + ] diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..04f44a9 --- /dev/null +++ b/app/models.py @@ -0,0 +1,27 @@ +from django.db import models + +# Create your models here. +class games(models.Model): + key = models.CharField(max_length=14, unique=True) + created_at = models.DateTimeField(auto_now_add=True) + state = models.BooleanField(default=True) + round_open = models.BooleanField(default=False) + + def __str__(self): + return self.key + +class Player(models.Model): + name = models.CharField(max_length=100) + game = models.ForeignKey(games, on_delete=models.CASCADE, related_name='players') + leader = models.BooleanField(default=False) + + def __str__(self): + return f"{self.name} ({self.game.key})" + +class answer(models.Model): + answer = models.CharField(max_length=250, unique=False, null=False) + player = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='answers') + akey = models.CharField(max_length=1, null=True, unique=False) + + def __str__(self): + return f"{self.answer}" \ No newline at end of file diff --git a/app/static/nip-app.png b/app/static/nip-app.png new file mode 100644 index 0000000..87a5ba7 Binary files /dev/null and b/app/static/nip-app.png differ diff --git a/app/templates/app/game.html b/app/templates/app/game.html new file mode 100644 index 0000000..4cce6fc --- /dev/null +++ b/app/templates/app/game.html @@ -0,0 +1,114 @@ +{% load static %} + + + + + Spielseite + + + + + + + + Logo + + {% if has_name %} + +

Spieler erkannt.
{{ player_data.name }}{% if player_data.leader %} - {{ countPlayers }} gesamt{% endif %}

+
+ + +
+
+ {% csrf_token %} + + + {% if player_data.leader %} + {% if round_open %} + + {% else %} + + + {% endif %} + {% endif %} + {% if round_open and not openAnswer %} +

+ + + {% endif %} +
+ {% else %} + +

Bitte Namen eingeben:

+
+ {% csrf_token %} + + +
+ {% endif %} + + + {% if player_data.leader %} + + + + + + + + + + + + + {% for answer in answers %} + + + + + + {% endfor %} + +
#SpielerAntwort
{{ answer.akey }}{{ answer.player.name }}{{ answer.answer }}
+ + {% endif %} + + + \ No newline at end of file diff --git a/app/templates/app/home.html b/app/templates/app/home.html new file mode 100644 index 0000000..5d62e9f --- /dev/null +++ b/app/templates/app/home.html @@ -0,0 +1,79 @@ +{% load static %} + + + + Nobody is Perfect | Die APP + + + + + + +
+ {% csrf_token %} + +
+ + + + + + + + + + + + {% for game in games %} + + + + + + + {% endfor %} + +
Spiel-IDKeyStatusAction
{{ game.id }}{{ game.key }}{{ game.state }}öffnen
+ + \ No newline at end of file diff --git a/app/tests.py b/app/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/urls.py b/app/urls.py new file mode 100644 index 0000000..6db7c8b --- /dev/null +++ b/app/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name='home'), + path('game', views.game, name='game'), +] \ No newline at end of file diff --git a/app/views.py b/app/views.py new file mode 100644 index 0000000..8e7aa83 --- /dev/null +++ b/app/views.py @@ -0,0 +1,106 @@ +from django.shortcuts import render, redirect +from django.urls import reverse +from django.utils.http import urlencode +import app.models as AppDB +import random +import string + +def random_alphabet(playerCount, listOfUsed): + full_alphabet = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "J") + game_alphabet = full_alphabet[:playerCount+1] + + # Verfügbare Buchstaben berechnen + available_letters = list(set(game_alphabet) - set(listOfUsed)) + + # Optional: sortieren (wenn Reihenfolge wichtig ist) + available_letters.sort() + + # Zufälligen Buchstaben wählen (falls verfügbar) + if available_letters: + chosen_letter = random.choice(available_letters) + print(f"Ausgewählter Buchstabe: {chosen_letter}") + return chosen_letter + else: + print("Keine Buchstaben mehr verfügbar.") + return "X" + + +def generate_key_view(): + key = ''.join(random.choices(string.ascii_uppercase + string.digits, k=14)) + + # Optional: Stelle sicher, dass der Key einzigartig ist + while AppDB.games.objects.filter(key=key).exists(): + key = ''.join(random.choices(string.ascii_uppercase + string.digits, k=14)) + + AppDB.games.objects.create(key=key) + + return key + +def home(request): + if request.method == "POST": + generate_key_view() + + requestGames = AppDB.games.objects.all() + return render(request, 'app/home.html', {'games':requestGames}) + +def game(request): + game_key = request.GET.get("key") + base_url = reverse("game") + query_string = urlencode({"key": game_key}) + game_instance = AppDB.games.objects.get(key=game_key) + player_name = request.session.get("device_name") + player_instance = AppDB.Player.objects.get(name=player_name) if player_name else None + openAnswer = True if AppDB.answer.objects.filter(player=player_instance).exists() else False + players_inGame = AppDB.Player.objects.filter(game=game_instance) + + if request.method == "POST": + if "btn_login_name" in request.POST: + input_PlayerName = request.POST.get("input_newPlayerName") + request.session["device_name"] = input_PlayerName + newEntry_Player = AppDB.Player() + + if not players_inGame.exists(): + newEntry_Player.leader = True + + newEntry_Player.game = game_instance + print(query_string) + newEntry_Player.name = input_PlayerName + newEntry_Player.save() + # Redirect mit key + return redirect(f"{base_url}?{query_string}") + + if "btn_logout" in request.POST: + toDeleteEntry = AppDB.Player.objects.get(name=player_name) + toDeleteEntry.delete() + request.session.flush() # oder: del request.session["device_name"] + return redirect(f"{base_url}?{query_string}") + + if "btn_start" in request.POST: + game_instance.round_open = True + game_instance.save() + + if "btn_stop" in request.POST: + game_instance.round_open = False + game_instance.save() + + if "btn_clear" in request.POST: + AppDB.answer.objects.filter(player__game=game_instance).delete() + + if "btn_sendAnswer" in request.POST: + sendAnswer = AppDB.answer() + sendAnswer.answer = request.POST.get("input_answer") + sendAnswer.player = player_instance + sendAnswer.akey = random_alphabet(players_inGame.count(), AppDB.answer.objects.filter(player__game=game_instance.id).values_list('akey', flat=True)) + sendAnswer.save() + openAnswer = False + return redirect(f"{base_url}?{query_string}") + + return render(request, "app/game.html", { + "has_name": player_name, + "game_key": game_key, + 'player_data': player_instance, + "round_open": game_instance.round_open, + "openAnswer": openAnswer, + "answers": AppDB.answer.objects.filter(player__game=game_instance.id).order_by("akey"), + 'countPlayers':players_inGame.count(), + }) \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000..7c2cc4f Binary files /dev/null and b/db.sqlite3 differ diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..25fcc8b --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NIP_APP.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main()