From ccf78c4820f005d2c679a8bc6c52616b6ed8490b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:07:40 +0200 Subject: [PATCH 01/66] Update Docker Compose to mount config volume as read-only --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index 1dadef9..95a2147 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,7 +6,7 @@ services: ports: - 35001:8000 volumes: - - /docker/musik/config:/config + - /docker/musik/config:/config:ro - /docker/musik/static:/app/static environment: CELERY_BROKER_URL: amqp://rabbitmq:5672// From 46225701e43b770fa4efdf771a6d6ed03003c05d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:09:13 +0200 Subject: [PATCH 02/66] Add env_file configuration for musik service in Docker Compose --- compose.yaml | 1 + stack.env | 0 2 files changed, 1 insertion(+) create mode 100644 stack.env diff --git a/compose.yaml b/compose.yaml index 95a2147..fe4cc9d 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,6 +10,7 @@ services: - /docker/musik/static:/app/static environment: CELERY_BROKER_URL: amqp://rabbitmq:5672// + env_file: stack.env depends_on: - rabbitmq - celery diff --git a/stack.env b/stack.env new file mode 100644 index 0000000..e69de29 From bcd3c723a8ad46d72f5c414c006da0ce47eaff2f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:13:43 +0200 Subject: [PATCH 03/66] Update static volume path in Docker Compose configuration --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index fe4cc9d..92f48f9 100644 --- a/compose.yaml +++ b/compose.yaml @@ -7,7 +7,7 @@ services: - 35001:8000 volumes: - /docker/musik/config:/config:ro - - /docker/musik/static:/app/static + - /srv/musik/static:/app/static environment: CELERY_BROKER_URL: amqp://rabbitmq:5672// env_file: stack.env From 6046b7326ce6878fc5ded22a19d4d79cc81e7733 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:19:36 +0200 Subject: [PATCH 04/66] Update DEBUG setting and configure allowed hosts in settings.py --- musik/settings.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/musik/settings.py b/musik/settings.py index 61c9c8f..0471604 100644 --- a/musik/settings.py +++ b/musik/settings.py @@ -24,10 +24,11 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = "django-insecure-&z*xu$^w8btr(%1!y#+0a98)l_q*+*6z54611pi678mdpsar_=" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = "DEBUG" in os.environ -ALLOWED_HOSTS = [] -CSRF_TRUSTED_ORIGINS = ["https://localhost"] +HOST = os.getenv("MUSIK_HOST", "localhost") +ALLOWED_HOSTS = [HOST] +CSRF_TRUSTED_ORIGINS = [f"https://{HOST}"] # Application definition From a0d6fb81cd5f0b1838be2d08aaeb0e1778a5e63f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:24:10 +0200 Subject: [PATCH 05/66] Update static volume path in Docker Compose configuration --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index 92f48f9..a4166dd 100644 --- a/compose.yaml +++ b/compose.yaml @@ -7,7 +7,7 @@ services: - 35001:8000 volumes: - /docker/musik/config:/config:ro - - /srv/musik/static:/app/static + - /data/srv/musik/static:/app/static environment: CELERY_BROKER_URL: amqp://rabbitmq:5672// env_file: stack.env From fc1ec45ab4aef89ddcda22b78ea53a1ccf064fdc Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:37:37 +0200 Subject: [PATCH 06/66] Refactor YouTube authentication flow and update settings for security enhancements --- game/views.py | 34 +++++++++++++++++----------------- musik/settings.py | 7 ++++++- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/game/views.py b/game/views.py index 3c434a4..f6632f0 100644 --- a/game/views.py +++ b/game/views.py @@ -305,22 +305,6 @@ class GameDetailView(LoginRequiredMixin, DetailView): ) -class YoutubeLoginView(LoginRequiredMixin, View): - def get(self, request): - flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( - settings.YOUTUBE_OAUTH_SECRETS, - ["https://www.googleapis.com/auth/youtube.force-ssl"], - ) - flow.redirect_uri = "https://localhost/youtube_callback/" - auth_url, state = flow.authorization_url( - access_type="offline", - include_granted_scopes="true", - prompt="consent", - ) - self.request.session["state"] = state - return redirect(auth_url) - - class YoutubeCallbackView(LoginRequiredMixin, View): def get(self, request): if err := request.GET.get("error"): @@ -335,7 +319,7 @@ class YoutubeCallbackView(LoginRequiredMixin, View): ["https://www.googleapis.com/auth/youtube.force-ssl"], state=state, ) - flow.redirect_uri = "https://localhost/youtube_callback/" + flow.redirect_uri = request.build_absolute_uri("/youtube_callback/") flow.fetch_token(code=request.GET.get("code")) @@ -358,6 +342,22 @@ class YoutubeCallbackView(LoginRequiredMixin, View): return redirect("/") +class YoutubeLoginView(LoginRequiredMixin, View): + def get(self, request): + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + settings.YOUTUBE_OAUTH_SECRETS, + ["https://www.googleapis.com/auth/youtube.force-ssl"], + ) + flow.redirect_uri = request.build_absolute_uri("/youtube_callback/") + auth_url, state = flow.authorization_url( + access_type="offline", + include_granted_scopes="true", + prompt="consent", + ) + self.request.session["state"] = state + return redirect(auth_url) + + class GroupClearBlacklistView(OwnerFilterMixin, SingleObjectMixin, View): model = models.Group diff --git a/musik/settings.py b/musik/settings.py index 0471604..f4aac85 100644 --- a/musik/settings.py +++ b/musik/settings.py @@ -21,7 +21,10 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-&z*xu$^w8btr(%1!y#+0a98)l_q*+*6z54611pi678mdpsar_=" +SECRET_KEY = os.getenv( + "MUSIK_SECRET_KEY", + "django-insecure-&z*xu$^w8btr(%1!y#+0a98)l_q*+*6z54611pi678mdpsar_=", +) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = "DEBUG" in os.environ @@ -29,6 +32,8 @@ DEBUG = "DEBUG" in os.environ HOST = os.getenv("MUSIK_HOST", "localhost") ALLOWED_HOSTS = [HOST] CSRF_TRUSTED_ORIGINS = [f"https://{HOST}"] +USE_X_FORWARDED_HOST = True +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # Application definition From c41c50e6730d32f8052d8572a8f617d228bf6884 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:41:48 +0200 Subject: [PATCH 07/66] Update Docker Compose and settings for PostgreSQL integration --- compose.yaml | 6 +++++ musik/settings.py | 7 ++++-- pyproject.toml | 1 + uv.lock | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/compose.yaml b/compose.yaml index a4166dd..06e1702 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,6 +10,7 @@ services: - /data/srv/musik/static:/app/static environment: CELERY_BROKER_URL: amqp://rabbitmq:5672// + POSTGRES_HOST: postgres env_file: stack.env depends_on: - rabbitmq @@ -28,3 +29,8 @@ services: rabbitmq: image: rabbitmq container_name: musik_rabbitmq + + postgres: + image: postgres:17 + container_name: musik_postgres + env_file: stack.env diff --git a/musik/settings.py b/musik/settings.py index f4aac85..f608bb0 100644 --- a/musik/settings.py +++ b/musik/settings.py @@ -84,8 +84,11 @@ WSGI_APPLICATION = "musik.wsgi.application" DATABASES = { "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", + "ENGINE": "django.db.backends.postgresql", + "HOST": os.getenv("POSTGRES_HOST", "localhost"), + "NAME": os.getenv("POSTGRES_DB", os.getenv("POSTGRES_USER", "musik")), + "USER": os.getenv("POSTGRES_USER", "musik"), + "PASSWORD": os.getenv("POSTGRES_PASSWORD"), } } diff --git a/pyproject.toml b/pyproject.toml index 204fa92..7656e66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "google-auth-httplib2>=0.2.0", "google-auth-oauthlib>=1.2.2", "gunicorn>=23.0.0", + "psycopg[binary]>=3.2.9", "requests>=2.32.4", ] diff --git a/uv.lock b/uv.lock index d4175cd..ce6f258 100644 --- a/uv.lock +++ b/uv.lock @@ -433,6 +433,7 @@ dependencies = [ { name = "google-auth-httplib2" }, { name = "google-auth-oauthlib" }, { name = "gunicorn" }, + { name = "psycopg", extra = ["binary"] }, { name = "requests" }, ] @@ -451,6 +452,7 @@ requires-dist = [ { name = "google-auth-httplib2", specifier = ">=0.2.0" }, { name = "google-auth-oauthlib", specifier = ">=1.2.2" }, { name = "gunicorn", specifier = ">=23.0.0" }, + { name = "psycopg", extras = ["binary"], specifier = ">=3.2.9" }, { name = "requests", specifier = ">=2.32.4" }, ] @@ -559,6 +561,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" }, ] +[[package]] +name = "psycopg" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/6f/ec9957e37a606cd7564412e03f41f1b3c3637a5be018d0849914cb06e674/psycopg_binary-3.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be7d650a434921a6b1ebe3fff324dbc2364393eb29d7672e638ce3e21076974e", size = 4022205, upload-time = "2025-05-13T16:07:48.195Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/497b8bea72b20a862ac95a94386967b745a472d9ddc88bc3f32d5d5f0d43/psycopg_binary-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76b4722a529390683c0304501f238b365a46b1e5fb6b7249dbc0ad6fea51a0", size = 4083795, upload-time = "2025-05-13T16:07:50.917Z" }, + { url = "https://files.pythonhosted.org/packages/42/07/af9503e8e8bdad3911fd88e10e6a29240f9feaa99f57d6fac4a18b16f5a0/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a551e4683f1c307cfc3d9a05fec62c00a7264f320c9962a67a543e3ce0d8ff", size = 4655043, upload-time = "2025-05-13T16:07:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/aff8c9850df1648cc6a5cc7a381f11ee78d98a6b807edd4a5ae276ad60ad/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61d0a6ceed8f08c75a395bc28cb648a81cf8dee75ba4650093ad1a24a51c8724", size = 4477972, upload-time = "2025-05-13T16:07:57.925Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/8e9d1b77ec1a632818fe2f457c3a65af83c68710c4c162d6866947d08cc5/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad280bbd409bf598683dda82232f5215cfc5f2b1bf0854e409b4d0c44a113b1d", size = 4737516, upload-time = "2025-05-13T16:08:01.616Z" }, + { url = "https://files.pythonhosted.org/packages/46/ec/222238f774cd5a0881f3f3b18fb86daceae89cc410f91ef6a9fb4556f236/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76eddaf7fef1d0994e3d536ad48aa75034663d3a07f6f7e3e601105ae73aeff6", size = 4436160, upload-time = "2025-05-13T16:08:04.278Z" }, + { url = "https://files.pythonhosted.org/packages/37/78/af5af2a1b296eeca54ea7592cd19284739a844974c9747e516707e7b3b39/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52e239cd66c4158e412318fbe028cd94b0ef21b0707f56dcb4bdc250ee58fd40", size = 3753518, upload-time = "2025-05-13T16:08:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ac/8a3ed39ea069402e9e6e6a2f79d81a71879708b31cc3454283314994b1ae/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08bf9d5eabba160dd4f6ad247cf12f229cc19d2458511cab2eb9647f42fa6795", size = 3313598, upload-time = "2025-05-13T16:08:09.999Z" }, + { url = "https://files.pythonhosted.org/packages/da/43/26549af068347c808fbfe5f07d2fa8cef747cfff7c695136172991d2378b/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1b2cf018168cad87580e67bdde38ff5e51511112f1ce6ce9a8336871f465c19a", size = 3407289, upload-time = "2025-05-13T16:08:12.66Z" }, + { url = "https://files.pythonhosted.org/packages/67/55/ea8d227c77df8e8aec880ded398316735add8fda5eb4ff5cc96fac11e964/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:14f64d1ac6942ff089fc7e926440f7a5ced062e2ed0949d7d2d680dc5c00e2d4", size = 3472493, upload-time = "2025-05-13T16:08:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/6ff2a5bc53c3cd653d281666728e29121149179c73fddefb1e437024c192/psycopg_binary-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:7a838852e5afb6b4126f93eb409516a8c02a49b788f4df8b6469a40c2157fa21", size = 2927400, upload-time = "2025-05-13T16:08:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" }, + { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" }, + { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" }, +] + [[package]] name = "pyasn1" version = "0.6.1" @@ -735,6 +784,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, +] + [[package]] name = "tzdata" version = "2025.2" From 8ef2c6c310066b7367bd82b720ac7186a44bb234 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:43:22 +0200 Subject: [PATCH 08/66] Add postgres to celery service dependencies in Docker Compose --- compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/compose.yaml b/compose.yaml index 06e1702..f0df675 100644 --- a/compose.yaml +++ b/compose.yaml @@ -15,6 +15,7 @@ services: depends_on: - rabbitmq - celery + - postgres celery: image: code.edgarpierre.fr/edpibu/musik From fc2cd08b266e5fcb155f134328bf2c454f2ed2ea Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:50:03 +0200 Subject: [PATCH 09/66] Add env_file configuration for celery service in Docker Compose --- compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/compose.yaml b/compose.yaml index f0df675..ab50ecb 100644 --- a/compose.yaml +++ b/compose.yaml @@ -24,6 +24,7 @@ services: command: uv run celery -A musik worker environment: CELERY_BROKER_URL: amqp://rabbitmq:5672// + env_file: stack.env depends_on: - rabbitmq From 3c401d4f7c08491dae049e300473d7aac8c1033a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 17:53:35 +0200 Subject: [PATCH 10/66] Add print statement to output DATABASES configuration for debugging --- musik/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/musik/settings.py b/musik/settings.py index f608bb0..3ec7bc7 100644 --- a/musik/settings.py +++ b/musik/settings.py @@ -91,7 +91,7 @@ DATABASES = { "PASSWORD": os.getenv("POSTGRES_PASSWORD"), } } - +print(DATABASES) # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators From d8f60f40179c2972d784c18df9601acd697ffdaf Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 18:03:35 +0200 Subject: [PATCH 11/66] Remove print statement from DATABASES configuration and ensure POSTGRES_HOST is set in celery service --- compose.yaml | 1 + musik/settings.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index ab50ecb..0510259 100644 --- a/compose.yaml +++ b/compose.yaml @@ -24,6 +24,7 @@ services: command: uv run celery -A musik worker environment: CELERY_BROKER_URL: amqp://rabbitmq:5672// + POSTGRES_HOST: postgres env_file: stack.env depends_on: - rabbitmq diff --git a/musik/settings.py b/musik/settings.py index 3ec7bc7..b12b987 100644 --- a/musik/settings.py +++ b/musik/settings.py @@ -91,7 +91,6 @@ DATABASES = { "PASSWORD": os.getenv("POSTGRES_PASSWORD"), } } -print(DATABASES) # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators From bf8161a7fbfc022b0cd87214345c5826c1da4e22 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 18:11:58 +0200 Subject: [PATCH 12/66] Add volume configuration for PostgreSQL service in Docker Compose --- compose.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compose.yaml b/compose.yaml index 0510259..04838ec 100644 --- a/compose.yaml +++ b/compose.yaml @@ -37,3 +37,5 @@ services: image: postgres:17 container_name: musik_postgres env_file: stack.env + volumes: + - /docker/musik/postgres:/var/lib/postgresql/data From 3069aab3f97b7ded13ed437012c736549c3074f5 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 18:14:33 +0200 Subject: [PATCH 13/66] Add delete group link to group detail page and update group creation header --- game/templates/game/group_detail.html | 4 ++++ game/templates/game/group_form.html | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/game/templates/game/group_detail.html b/game/templates/game/group_detail.html index 9931d35..b6f48d8 100644 --- a/game/templates/game/group_detail.html +++ b/game/templates/game/group_detail.html @@ -25,6 +25,10 @@ formaction="{% url "group_clear_blacklist" pk=group.pk %}"> Effacer la blacklist + + Supprimer

diff --git a/game/templates/game/group_form.html b/game/templates/game/group_form.html index 72ae8d9..ef6da8c 100644 --- a/game/templates/game/group_form.html +++ b/game/templates/game/group_form.html @@ -5,9 +5,6 @@

{{ group.name }}

-

- Supprimer le groupe -

{% else %}

Créer un groupe

{% endif %} From fbe24915fe4e5b6b7a46eacb3e1637a57b7f9208 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 18:25:24 +0200 Subject: [PATCH 14/66] Update signup view to use reverse_lazy for success URL --- base/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/views.py b/base/views.py index e3618a0..29eb8b3 100644 --- a/base/views.py +++ b/base/views.py @@ -1,5 +1,6 @@ from django.contrib.auth.models import User from django.contrib.messages.views import SuccessMessageMixin +from django.urls import reverse_lazy from django.views.generic.base import TemplateView from django.views.generic.edit import CreateView @@ -13,5 +14,5 @@ class HomePageView(TemplateView): class SignupView(SuccessMessageMixin, CreateView): model = User form_class = forms.UserSignupForm - success_url = "/" + success_url = reverse_lazy("login") success_message = "Le compte %(username)s a été créé avec succès." From ac07607dd0208a932b0b96af7537d771d15d6886 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 19:00:01 +0200 Subject: [PATCH 15/66] Add privacy policy page and update URLs configuration --- base/templates/privacy.html | 21 +++++++++++++++++++++ base/urls.py | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 base/templates/privacy.html diff --git a/base/templates/privacy.html b/base/templates/privacy.html new file mode 100644 index 0000000..8e78465 --- /dev/null +++ b/base/templates/privacy.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% block content %} +

Mentions légales

+

Éditeur

+

+ Ce site est réalisé et hébergé par Edgar P. Burkhart. +

+

Données Personnelles

+

+ Dans le cadre de la création d'un compte, un nom d'utilisateur, une adresse email et un mot de passe sont requis. + Ces données sont stockées dans le seul but de permettre la connexion et la réinitialisation du mot de passe de l'utilisateur (dans le cas de l'adresse mail). +

+

+ La connexion à un compte Youtube est utilisée pour permettre la génération automatique de playlists, et sa suppression le cas échéant. + Elle n'est pas requise pour l'utilisation de Musik. + Youtube est une marque de Google LLC. +

+

+ La suppression des données stockée par le service Musik pour son utilisation peut être demandée par email à Edgar P. Burkhart. +

+{% endblock content %} diff --git a/base/urls.py b/base/urls.py index 2a6b2d5..e46107f 100644 --- a/base/urls.py +++ b/base/urls.py @@ -1,4 +1,5 @@ from django.urls import include, path +from django.views.generic import TemplateView from . import views @@ -6,4 +7,5 @@ urlpatterns = [ path("", views.HomePageView.as_view(), name="index"), path("accounts/signup/", views.SignupView.as_view(), name="signup"), path("accounts/", include("django.contrib.auth.urls")), + path("legal/", TemplateView.as_view(template_name="privacy.html")), ] From 5c99306f7429fcdd3fe5a5dc30dd75b9a8aad60f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 19:05:53 +0200 Subject: [PATCH 16/66] Add footer template and include it in base and hero templates; update legal URL path Signed-off-by: Edgar P. Burkhart --- base/static/css/main.css | 6 ++++++ base/templates/base.html | 3 +++ base/templates/footer.html | 1 + base/templates/hero.html | 7 +++++-- base/urls.py | 2 +- 5 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 base/templates/footer.html diff --git a/base/static/css/main.css b/base/static/css/main.css index c329a18..99b0900 100644 --- a/base/static/css/main.css +++ b/base/static/css/main.css @@ -91,6 +91,7 @@ article.message { color-mix(in hsl, var(--pico-primary-background) 10%, var(--pico-background-color)) 80%, var(--pico-background-color)); display: grid; + grid-template-rows: 1fr min-content; align-items: center; padding: 4rem; @@ -116,3 +117,8 @@ h6, i.i { margin-right: .5em; } + +footer { + text-align: center; + font-weight: 350; +} diff --git a/base/templates/base.html b/base/templates/base.html index 8e677eb..8c84913 100644 --- a/base/templates/base.html +++ b/base/templates/base.html @@ -68,6 +68,9 @@ {% block content %} {% endblock content %} +
+ {% include "footer.html" %} +
{% endblock body %} diff --git a/base/templates/footer.html b/base/templates/footer.html new file mode 100644 index 0000000..552662c --- /dev/null +++ b/base/templates/footer.html @@ -0,0 +1 @@ +© Edgar P. BurkhartMentions légales diff --git a/base/templates/hero.html b/base/templates/hero.html index 7dcdbe0..34591ba 100644 --- a/base/templates/hero.html +++ b/base/templates/hero.html @@ -1,10 +1,13 @@ {% load static %}
-
+

Musik

Jouer

-
+ +
+ {% include "footer.html" %} +
diff --git a/base/urls.py b/base/urls.py index e46107f..ea1f6ba 100644 --- a/base/urls.py +++ b/base/urls.py @@ -7,5 +7,5 @@ urlpatterns = [ path("", views.HomePageView.as_view(), name="index"), path("accounts/signup/", views.SignupView.as_view(), name="signup"), path("accounts/", include("django.contrib.auth.urls")), - path("legal/", TemplateView.as_view(template_name="privacy.html")), + path("legal/", TemplateView.as_view(template_name="privacy.html"), name="legal"), ] From 6cd9c0c8414ee1854753304ab6e6b1ef40bea755 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 19:06:19 +0200 Subject: [PATCH 17/66] Bump version to 0.1.1 in pyproject.toml Signed-off-by: Edgar P. Burkhart --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7656e66..ca9f4df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "musik" -version = "0.1.0" +version = "0.1.1" description = "Le jeu de Musik." readme = "README.md" requires-python = ">=3.12" From 7409b4cd8fe08fcd8a6ee285163604e5d82c67bb Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 21:58:33 +0200 Subject: [PATCH 18/66] Add group leader functionality and update group member management - Create GroupLeader model and migration - Alter GroupLeader member field to include related_name - Implement is_leader and is_owner methods in Group model - Update GroupDetailView to pass leader and owner status to template - Refactor group buttons and members display into separate templates - Add view and URL for setting group leaders - Update permissions for adding/removing members and clearing blacklist - Bump version to 0.1.1 in uv.lock --- base/static/css/main.css | 8 ++ game/migrations/0015_groupleader.py | 35 ++++++++ .../0016_alter_groupleader_member.py | 22 +++++ game/models.py | 21 +++++ game/templates/game/group_detail.html | 88 +------------------ .../templates/game/include/group_buttons.html | 28 ++++++ .../templates/game/include/group_members.html | 79 +++++++++++++++++ game/urls.py | 5 ++ game/views.py | 57 +++++++++--- uv.lock | 2 +- 10 files changed, 244 insertions(+), 101 deletions(-) create mode 100644 game/migrations/0015_groupleader.py create mode 100644 game/migrations/0016_alter_groupleader_member.py create mode 100644 game/templates/game/include/group_buttons.html create mode 100644 game/templates/game/include/group_members.html diff --git a/base/static/css/main.css b/base/static/css/main.css index 99b0900..c6146bc 100644 --- a/base/static/css/main.css +++ b/base/static/css/main.css @@ -122,3 +122,11 @@ footer { text-align: center; font-weight: 350; } + +td.c, th.c { + text-align: center; + + input { + margin: 0; + } +} diff --git a/game/migrations/0015_groupleader.py b/game/migrations/0015_groupleader.py new file mode 100644 index 0000000..a9e5dc2 --- /dev/null +++ b/game/migrations/0015_groupleader.py @@ -0,0 +1,35 @@ +# Generated by Django 5.2.3 on 2025-06-14 18:50 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("game", "0014_musikgame_playlist_loading"), + ] + + operations = [ + migrations.CreateModel( + name="GroupLeader", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_leader", models.BooleanField(default=False)), + ( + "member", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to="game.group_members", + ), + ), + ], + ), + ] diff --git a/game/migrations/0016_alter_groupleader_member.py b/game/migrations/0016_alter_groupleader_member.py new file mode 100644 index 0000000..1561a34 --- /dev/null +++ b/game/migrations/0016_alter_groupleader_member.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.3 on 2025-06-14 18:50 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("game", "0015_groupleader"), + ] + + operations = [ + migrations.AlterField( + model_name="groupleader", + name="member", + field=models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="lead", + to="game.group_members", + ), + ), + ] diff --git a/game/models.py b/game/models.py index 88fe6cc..a3f104d 100644 --- a/game/models.py +++ b/game/models.py @@ -23,12 +23,33 @@ class Group(models.Model): def get_absolute_url(self): return reverse("group_detail", kwargs={"pk": self.pk}) + def is_owner(self, user): + return user == self.owner + + def is_leader(self, user): + return ( + self.is_owner(user) + or self.members.through.objects.filter( + lead__is_leader=True, user=user + ).exists() + ) + + def is_member(self, user): + return self.is_owner(user) or self.members.filter(user=user).exists() + class Meta: constraints = [ models.UniqueConstraint(Lower("name"), "owner", name="unique_group_name") ] +class GroupLeader(models.Model): + member = models.OneToOneField( + Group.members.through, on_delete=models.CASCADE, related_name="lead" + ) + is_leader = models.BooleanField(default=False) + + class MusicVideo(models.Model): yt_id = models.CharField(max_length=16) title = models.CharField(blank=True) diff --git a/game/templates/game/group_detail.html b/game/templates/game/group_detail.html index b6f48d8..d523f29 100644 --- a/game/templates/game/group_detail.html +++ b/game/templates/game/group_detail.html @@ -9,30 +9,7 @@ {% endif %} {{ group.name }} - {% if group.owner == user %} -

-

- {% csrf_token %} -

- Jouer -

-
- Renommer - - - Supprimer -
-
-

- {% endif %} + {% include "game/include/group_buttons.html" %} {% if group.musikgame_set.exists %}

Parties @@ -79,68 +56,7 @@ {% endif %} {% endif %} -

- Membres -

-
- {% csrf_token %} - - - - {% if group.owner == user %}{% endif %} - - - - - - - - {% if group.owner == user %}{% endif %} - - - - - {% for member in members.all %} - - {% if group.owner == user %} - - {% endif %} - - - - - {% endfor %} - -
Membre - - - -
{{ group.owner }} - - {{ owner_count }}
- - {{ member }}{{ member.count }}
- {% if group.owner == user %} - - {% endif %} -
- {% if group.owner == user %} -
- {% csrf_token %} -
- - -
-
- {% endif %} + {% include "game/include/group_members.html" %}

Mes musiques {{ musics.count }}

diff --git a/game/templates/game/include/group_buttons.html b/game/templates/game/include/group_buttons.html new file mode 100644 index 0000000..065545f --- /dev/null +++ b/game/templates/game/include/group_buttons.html @@ -0,0 +1,28 @@ +{% if is_leader %} +

+

+ {% csrf_token %} +

+ Jouer +

+
+ {% if is_owner %} + Renommer + {% endif %} + + {% if is_owner %} + + Supprimer + {% endif %} +
+
+

+{% endif %} diff --git a/game/templates/game/include/group_members.html b/game/templates/game/include/group_members.html new file mode 100644 index 0000000..f49131c --- /dev/null +++ b/game/templates/game/include/group_members.html @@ -0,0 +1,79 @@ +

+ Membres +

+
+ {% csrf_token %} + + + + {% if is_leader %}{% endif %} + + + + + + + + {% if is_leader %}{% endif %} + + + + + {% for member in members.all %} + + {% if is_leader %} + + {% endif %} + + + + + {% endfor %} + +
Membre + + + +
{{ group.owner }} + + {{ owner_count }}
+ + {{ member.user }} + {% if is_owner %} + + {% endif %} + {{ member.count }}
+ {% if is_leader %} +
+ {% if is_owner %} + + {% endif %} + +
+ {% endif %} +
+{% if is_leader %} +
+ {% csrf_token %} +
+ + +
+
+{% endif %} diff --git a/game/urls.py b/game/urls.py index 763d277..2f4c3fa 100644 --- a/game/urls.py +++ b/game/urls.py @@ -41,6 +41,11 @@ urlpatterns = [ views.GroupRemoveMemberView.as_view(), name="group_remove_member", ), + path( + "group//set_leader/", + views.GroupSetLead.as_view(), + name="group_set_leader", + ), path( "group//start_game/", views.GameCreateView.as_view(), name="start_game" ), diff --git a/game/views.py b/game/views.py index f6632f0..ef10245 100644 --- a/game/views.py +++ b/game/views.py @@ -6,6 +6,7 @@ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.contrib.messages.views import SuccessMessageMixin +from django.core.exceptions import PermissionDenied from django.db import IntegrityError from django.db.models import Count, Q from django.shortcuts import get_object_or_404, redirect @@ -70,14 +71,17 @@ class GroupDetailView(MemberFilterMixin, GroupMixin, DetailView): .musicvideo_set.filter(owner=data["group"].owner, blacklisted=False) .count() ) - data["members"] = data["group"].members.annotate( + data["members"] = data["group"].members.through.objects.annotate( count=Count( - "musicvideo", + "user__musicvideo", filter=Q( - musicvideo__group=data["group"], musicvideo__blacklisted=False + user__musicvideo__group=data["group"], + user__musicvideo__blacklisted=False, ), ) ) + data["is_leader"] = data["group"].is_leader(self.request.user) + data["is_owner"] = data["group"].is_owner(self.request.user) return data @@ -112,11 +116,13 @@ class GroupAddMusicView(MemberFilterMixin, SingleObjectMixin, View): return redirect(group) -class GroupAddMemberView(OwnerFilterMixin, SingleObjectMixin, View): +class GroupAddMemberView(MemberFilterMixin, SingleObjectMixin, View): model = models.Group def post(self, request, pk): group = self.get_object() + if not group.is_leader(request.user): + raise PermissionDenied() username = request.POST.get("username") user = User.objects.get(username=username) if user == group.owner: @@ -180,14 +186,16 @@ class GroupUnblacklistMusicView(MemberFilterMixin, SingleObjectMixin, View): return redirect(group) -class GroupRemoveMemberView(OwnerFilterMixin, SingleObjectMixin, View): +class GroupRemoveMemberView(MemberFilterMixin, SingleObjectMixin, View): model = models.Group def post(self, request, pk): group = self.get_object() + if not group.is_leader(request.user): + raise PermissionDenied() relations = models.Group.members.through.objects.filter( - group=group, user__id__in=request.POST.getlist("member") + group=group, pk__in=request.POST.getlist("member") ) if relations.count() == 0: messages.add_message(request, messages.INFO, "Aucun membre supprimé.") @@ -210,6 +218,25 @@ class GroupRemoveMemberView(OwnerFilterMixin, SingleObjectMixin, View): return redirect(group) +class GroupSetLead(OwnerFilterMixin, SingleObjectMixin, View): + model = models.Group + + def post(self, request, pk): + group = self.get_object() + + members = models.Group.members.through.objects.filter(group=group) + for member in members.filter(pk__in=request.POST.getlist("leader")): + models.GroupLeader.objects.update_or_create( + member=member, defaults={"is_leader": True} + ) + for member in members.exclude(pk__in=request.POST.getlist("leader")): + models.GroupLeader.objects.update_or_create( + member=member, defaults={"is_leader": False} + ) + + return redirect(group) + + class GroupRemoveGameView(OwnerFilterMixin, SingleObjectMixin, View): model = models.Group @@ -250,15 +277,15 @@ class GameCreateView(LoginRequiredMixin, CreateView): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) - data["group"] = get_object_or_404( - models.Group, owner=self.request.user, pk=self.kwargs["pk"] - ) + data["group"] = get_object_or_404(models.Group, pk=self.kwargs["pk"]) + if not data["group"].is_leader(self.request.user): + raise PermissionDenied() return data def form_valid(self, form): - group = get_object_or_404( - models.Group, owner=self.request.user, pk=self.kwargs["pk"] - ) + group = get_object_or_404(models.Group, pk=self.kwargs["pk"]) + if not group.is_leader(self.request.user): + return super().form_invalid(form) form.instance.group = group res = super().form_valid(form) players = [] @@ -285,7 +312,7 @@ class GameCreateView(LoginRequiredMixin, CreateView): game=form.instance, player=player, music_video=music, order=order ) - if self.request.user.youtubecredentials: + if models.YoutubeCredentials.objects.filter(user=self.request.user).exists(): form.instance.playlist_loading = True form.instance.save() return res @@ -358,11 +385,13 @@ class YoutubeLoginView(LoginRequiredMixin, View): return redirect(auth_url) -class GroupClearBlacklistView(OwnerFilterMixin, SingleObjectMixin, View): +class GroupClearBlacklistView(MemberFilterMixin, SingleObjectMixin, View): model = models.Group def post(self, request, pk): group = self.get_object() + if not group.is_leader(request.user): + raise PermissionDenied() group.musicvideo_set.filter(blacklisted=True).update(blacklisted=False) messages.add_message(request, messages.SUCCESS, "La blacklist a été effacée.") return redirect(group) diff --git a/uv.lock b/uv.lock index ce6f258..72a1f51 100644 --- a/uv.lock +++ b/uv.lock @@ -423,7 +423,7 @@ wheels = [ [[package]] name = "musik" -version = "0.1.0" +version = "0.1.1" source = { virtual = "." } dependencies = [ { name = "celery" }, From caaf704bd9f7cd376dd9b49c5d8f6a032f16af2c Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 21:58:52 +0200 Subject: [PATCH 19/66] Bump version to 0.1.2 in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ca9f4df..4a7ce47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "musik" -version = "0.1.1" +version = "0.1.2" description = "Le jeu de Musik." readme = "README.md" requires-python = ">=3.12" From a6a8249d4ed70ba1ae40533c295f7b65b3181087 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 22:00:48 +0200 Subject: [PATCH 20/66] Add visual indicator for group leader status in member list --- game/templates/game/include/group_members.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/game/templates/game/include/group_members.html b/game/templates/game/include/group_members.html index f49131c..3b668e5 100644 --- a/game/templates/game/include/group_members.html +++ b/game/templates/game/include/group_members.html @@ -40,6 +40,8 @@ value="{{ member.pk }}" role="switch" {% if member.lead.is_leader %}checked{% endif %}> + {% elif member.lead.is_leader %} + {% endif %} {{ member.count }} From 60de28464e837ffd76a27bd035857267b0854a94 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 22:00:59 +0200 Subject: [PATCH 21/66] Bump version to 0.1.3 in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4a7ce47..fea27c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "musik" -version = "0.1.2" +version = "0.1.3" description = "Le jeu de Musik." readme = "README.md" requires-python = ">=3.12" From f3e914aed8451cb9a7d220ce3d2e3533689f96b0 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 14 Jun 2025 22:04:39 +0200 Subject: [PATCH 22/66] Bump version to 0.1.4 in pyproject.toml and uv.lock --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fea27c6..0b7faaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "musik" -version = "0.1.3" +version = "0.1.4" description = "Le jeu de Musik." readme = "README.md" requires-python = ">=3.12" diff --git a/uv.lock b/uv.lock index 72a1f51..0f40f57 100644 --- a/uv.lock +++ b/uv.lock @@ -423,7 +423,7 @@ wheels = [ [[package]] name = "musik" -version = "0.1.1" +version = "0.1.4" source = { virtual = "." } dependencies = [ { name = "celery" }, From 088bb52c07c869310b6094ede232bfd394bdacd6 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 15 Jun 2025 09:56:08 +0200 Subject: [PATCH 23/66] Implement group management features and update templates for improved navigation --- base/templates/base/inline_form.html | 11 ++++++ base/templates/hero.html | 2 +- base/templates/index.html | 9 +---- base/templatetags/form.py | 11 ++++-- base/views.py | 6 +++ game/forms.py | 10 +++++ game/templates/game/home.html | 57 ++++++++++++++++------------ game/urls.py | 1 + game/views.py | 10 +++++ 9 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 base/templates/base/inline_form.html diff --git a/base/templates/base/inline_form.html b/base/templates/base/inline_form.html new file mode 100644 index 0000000..75769ae --- /dev/null +++ b/base/templates/base/inline_form.html @@ -0,0 +1,11 @@ +{% for error in form.non_field_errors %}
{{ error }}
{% endfor %} +
+ {% csrf_token %} +
+ {% for field in form %}{{ field }}{% endfor %} + + {% if field.errors %} + {{ field.errors|join:", " }} + {% endif %} +
+
diff --git a/base/templates/hero.html b/base/templates/hero.html index 34591ba..04c2aee 100644 --- a/base/templates/hero.html +++ b/base/templates/hero.html @@ -4,7 +4,7 @@

Musik

- Jouer + Jouer