From 66f1c0d931452f5e0bcbc1d4970191157073cdc8 Mon Sep 17 00:00:00 2001 From: Karl_Sue Date: Tue, 25 Nov 2025 23:30:43 +0000 Subject: [PATCH 01/69] feat: add Art model --- server/game_dev/models.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 9c26a564..54e9f298 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -10,3 +10,14 @@ class Member(models.Model): def __str__(self): return str(self.name) + + +class Art(models.Model): + name = models.CharField(null=False, max_length=200) + description = models.CharField(max_length=200,) + source_game = models.ForeignKey(Games, on_delete=models.CASCADE, related_name='art_pieces') #Need implement Games model + path_to_media = models.CharField(null=False) + active = models.BooleanField(null=False) + + def __str__(self): + return str(self.name) From ddbf780db22c50870d255a7ccdb4f7ca614fdbc2 Mon Sep 17 00:00:00 2001 From: Karl_Sue Date: Wed, 26 Nov 2025 11:02:02 +0000 Subject: [PATCH 02/69] refactor: keep model register minimal --- server/game_dev/admin.py | 5 ++++- server/game_dev/models.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/server/game_dev/admin.py b/server/game_dev/admin.py index 4185d360..69865a64 100644 --- a/server/game_dev/admin.py +++ b/server/game_dev/admin.py @@ -1,3 +1,6 @@ -# from django.contrib import admin +from django.contrib import admin + +from .models import Art # Register your models here. +admin.site.register(Art) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 54e9f298..6fa4bb1b 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -15,7 +15,7 @@ def __str__(self): class Art(models.Model): name = models.CharField(null=False, max_length=200) description = models.CharField(max_length=200,) - source_game = models.ForeignKey(Games, on_delete=models.CASCADE, related_name='art_pieces') #Need implement Games model + # source_game = models.ForeignKey(Games, on_delete=models.CASCADE, related_name='art_pieces') #Need implement Games model path_to_media = models.CharField(null=False) active = models.BooleanField(null=False) From b26d12de0d3bc241da422f1d20519817f75d8e2d Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Fri, 28 Nov 2025 17:43:29 +0000 Subject: [PATCH 03/69] Add ArtContributor model with API endpoints --- server/api/urls.py | 5 +- server/game_dev/admin.py | 24 ++++++- .../migrations/0002_art_artcontributor.py | 68 +++++++++++++++++++ server/game_dev/models.py | 14 +++- server/game_dev/serializers.py | 11 +++ server/game_dev/urls.py | 10 +++ server/game_dev/views.py | 9 ++- 7 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 server/game_dev/migrations/0002_art_artcontributor.py create mode 100644 server/game_dev/serializers.py create mode 100644 server/game_dev/urls.py diff --git a/server/api/urls.py b/server/api/urls.py index 1347bb73..c6a16187 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -20,5 +20,6 @@ urlpatterns = [ path("admin/", admin.site.urls), - path("api/healthcheck/", include(("api.healthcheck.urls"))), -] + path("api/healthcheck/", include("api.healthcheck.urls")), + path("api/game-dev/", include("game_dev.urls")), +] \ No newline at end of file diff --git a/server/game_dev/admin.py b/server/game_dev/admin.py index 69865a64..2cfeab2e 100644 --- a/server/game_dev/admin.py +++ b/server/game_dev/admin.py @@ -1,6 +1,24 @@ from django.contrib import admin +from .models import Member, Art, ArtContributor -from .models import Art -# Register your models here. -admin.site.register(Art) +@admin.register(Member) +class MemberAdmin(admin.ModelAdmin): + list_display = ['name', 'active', 'pronouns'] + list_filter = ['active'] + search_fields = ['name'] + + +@admin.register(Art) +class ArtAdmin(admin.ModelAdmin): + list_display = ['name', 'active'] + list_filter = ['active'] + search_fields = ['name'] + + +@admin.register(ArtContributor) +class ArtContributorAdmin(admin.ModelAdmin): + list_display = ['art', 'member', 'role'] + list_filter = ['role'] + search_fields = ['member__name', 'art__name'] + autocomplete_fields = ['art', 'member'] \ No newline at end of file diff --git a/server/game_dev/migrations/0002_art_artcontributor.py b/server/game_dev/migrations/0002_art_artcontributor.py new file mode 100644 index 00000000..138685d9 --- /dev/null +++ b/server/game_dev/migrations/0002_art_artcontributor.py @@ -0,0 +1,68 @@ +# Generated by Django 5.1.14 on 2025-11-28 17:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Art", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200)), + ("description", models.CharField(max_length=200)), + ("path_to_media", models.CharField(max_length=500)), + ("active", models.BooleanField()), + ], + ), + migrations.CreateModel( + name="ArtContributor", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("role", models.CharField(max_length=100)), + ( + "art", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="contributors", + to="game_dev.art", + ), + ), + ( + "member", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="art_contributions", + to="game_dev.member", + ), + ), + ], + options={ + "verbose_name": "Art Contributor", + "verbose_name_plural": "Art Contributors", + "unique_together": {("art", "member")}, + }, + ), + ] diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 6fa4bb1b..ea132d6b 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -16,8 +16,20 @@ class Art(models.Model): name = models.CharField(null=False, max_length=200) description = models.CharField(max_length=200,) # source_game = models.ForeignKey(Games, on_delete=models.CASCADE, related_name='art_pieces') #Need implement Games model - path_to_media = models.CharField(null=False) + path_to_media = models.CharField(null=False, max_length=500) active = models.BooleanField(null=False) def __str__(self): return str(self.name) + +class ArtContributor(models.Model): + art = models.ForeignKey('Art', on_delete=models.CASCADE, related_name='contributors') + member = models.ForeignKey('Member', on_delete=models.CASCADE, related_name='art_contributions') + role = models.CharField(max_length=100) + class Meta: + unique_together = ('art', 'member') + verbose_name = 'Art Contributor' + verbose_name_plural = 'Art Contributors' + + def __str__(self): + return f"{self.member.name} - {self.art.name} ({self.role})" diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py new file mode 100644 index 00000000..768a4bfa --- /dev/null +++ b/server/game_dev/serializers.py @@ -0,0 +1,11 @@ +from rest_framework import serializers +from .models import ArtContributor + + +class ArtContributorSerializer(serializers.ModelSerializer): + member_name = serializers.CharField(source='member.name', read_only=True) + art_name = serializers.CharField(source='art.name', read_only=True) + + class Meta: + model = ArtContributor + fields = ['id', 'art', 'member', 'member_name', 'art_name', 'role'] \ No newline at end of file diff --git a/server/game_dev/urls.py b/server/game_dev/urls.py new file mode 100644 index 00000000..d1799496 --- /dev/null +++ b/server/game_dev/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import ArtContributorViewSet + +router = DefaultRouter() +router.register(r'art-contributors', ArtContributorViewSet, basename='artcontributor') + +urlpatterns = [ + path('', include(router.urls)), +] \ No newline at end of file diff --git a/server/game_dev/views.py b/server/game_dev/views.py index fd0e0449..264448f7 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -1,3 +1,8 @@ -# from django.shortcuts import render +from rest_framework import viewsets +from .models import ArtContributor +from .serializers import ArtContributorSerializer -# Create your views here. + +class ArtContributorViewSet(viewsets.ModelViewSet): + queryset = ArtContributor.objects.all() + serializer_class = ArtContributorSerializer \ No newline at end of file From 8ac4927eec7caa6c8085a1568bad2be7c58ef2dd Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Fri, 5 Dec 2025 20:19:11 +0800 Subject: [PATCH 04/69] art-page-frontend --- client/src/pages/artwork/[id].tsx | 223 +++++++++++++++++++++++++++++ client/src/pages/artwork/index.tsx | 131 +++++++++++++++++ client/src/styles/globals.css | 25 ++++ 3 files changed, 379 insertions(+) create mode 100644 client/src/pages/artwork/[id].tsx create mode 100644 client/src/pages/artwork/index.tsx diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx new file mode 100644 index 00000000..897ccead --- /dev/null +++ b/client/src/pages/artwork/[id].tsx @@ -0,0 +1,223 @@ +import { GetServerSideProps } from "next"; +import Image from "next/image"; +import Link from "next/link"; + +type Contributor = { + id: string; + name: string; + instagramUrl?: string; + discordUrl?: string; +}; +type Artwork = { + id: string; + name: string; + description: string; + sourceGame: string; + pathToMedia: string; + active: boolean; + createdAt: string; + contributors?: Contributor[]; +}; + +interface ArtworkPageProps { + artwork: Artwork; +} + +export default function ArtworkPage({ artwork }: ArtworkPageProps) { + return ( +
+
+ TODO add Header +
+
+
+ < Gallery +
+
+
+
+
+ + + +
+
+
+
+
+ {artwork.name} +
+
+
+ + {artwork.description} + +
+
+
+
+
+
+ Contributors +
+
+
+ {artwork.contributors?.map((contributor) => ( +
+
+ {contributor.name} +
+
+ {contributor.discordUrl ? ( +
+ + + +
+ ) : ( + "" + )} + {contributor.instagramUrl ? ( +
+ + + +
+ ) : ( + "" + )} +
+
+ ))} +
+
+
+
+
+
+ +
+
+ Game Image +
+
+
+ TODO add footer +
+
+ ); +} + +export const getServerSideProps: GetServerSideProps = async ( + context, +) => { + const { id } = context.params as { id: string }; + console.log("Fetching artwork with id:", id); + // const res = await fetch(`https://your-backend.com/api/artwork/${id}`); + const artwork: Artwork = { + id: "abc of art", + name: "title of art", + description: "description of art", + sourceGame: "", + pathToMedia: "", + active: false, + createdAt: new Date().toISOString(), + contributors: [ + { id: "1", name: "Contributor 1", discordUrl: "discordUrl" }, + { id: "2", name: "Contributor 2", instagramUrl: "instagramUrl" }, + ], + }; + + return { props: { artwork } }; +}; diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx new file mode 100644 index 00000000..b3e6d926 --- /dev/null +++ b/client/src/pages/artwork/index.tsx @@ -0,0 +1,131 @@ +import { GetServerSideProps } from "next"; +import Link from "next/link"; + +type Artwork = { + id: string; + name: string; + description: string; + sourceGame: string; + pathToMedia: string; + active: boolean; + createdAt: string; +}; + +interface ArtworksPageProps { + artworks: Artwork[]; +} + +const PLACEHOLDER_ICON = ( +
+ + + +
+); + +function renderArtworkCard(artwork: Artwork) { + return ( + +
+ {PLACEHOLDER_ICON} +
+ + ); +} + +export default function ArtworksPage({ artworks }: ArtworksPageProps) { + return ( +
+
+ TODO add Header +
+
+
+ FEATURED: +
+ SOME GAME +
+
+ {PLACEHOLDER_ICON} +
+
+
+
+ More about us → +
+
+
+
+ +
+
+ {artworks.map((artwork) => renderArtworkCard(artwork))} +
+
+
+ TODO add footer +
+
+ ); +} + +export const getServerSideProps: GetServerSideProps< + ArtworksPageProps +> = async () => { + // const res = await fetch(`https://your-backend.com/api/artwork/${id}`); + const artworks: Artwork[] = []; + for (let i = 0; i < 12; i++) { + const artwork: Artwork = { + id: i + "", + name: "title of art" + i, + description: "description of art", + sourceGame: "", + pathToMedia: "", + active: false, + createdAt: new Date().toISOString(), + }; + artworks.push(artwork); + } + + return { props: { artworks } }; +}; diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index 616c2b14..9079808f 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -33,6 +33,12 @@ --ring: 236 47% 7%; --radius: 0.5rem; + + --dark-2: #090A19; + --neutral-1: #1B1F4C; + --light-1: #FFFFFF; + --light-2: #CED1FE; + --light-3: #9CA4FD; } } @@ -41,3 +47,22 @@ @apply bg-background text-foreground; } } + +.bg-neutral-1 { + background-color: var(--neutral-1); +} +.bg-dark-2 { + background-color: var(--dark-2); +} +.bg-light-2 { + background-color: var(--light-2); +} +.text-light-1 { + color: var(--light-1); +} +.outline-neutral-1 { + outline-color: var(--neutral-1); +} +.text-light-3 { + color: var(--light-3); +} \ No newline at end of file From 8b98a06352b5aaa6c1a2e7ebe30dd27ef9e199e8 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Fri, 5 Dec 2025 20:19:11 +0800 Subject: [PATCH 05/69] art-page-frontend --- client/public/placeholder1293x405.svg | 1 + client/src/pages/artwork/[id].tsx | 225 ++++++++++++++++++++++++++ client/src/pages/artwork/index.tsx | 131 +++++++++++++++ client/src/styles/globals.css | 25 +++ 4 files changed, 382 insertions(+) create mode 100644 client/public/placeholder1293x405.svg create mode 100644 client/src/pages/artwork/[id].tsx create mode 100644 client/src/pages/artwork/index.tsx diff --git a/client/public/placeholder1293x405.svg b/client/public/placeholder1293x405.svg new file mode 100644 index 00000000..34f928c8 --- /dev/null +++ b/client/public/placeholder1293x405.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx new file mode 100644 index 00000000..732f645b --- /dev/null +++ b/client/src/pages/artwork/[id].tsx @@ -0,0 +1,225 @@ +import { GetServerSideProps } from "next"; +import Image from "next/image"; +import Link from "next/link"; + +type Contributor = { + id: string; + name: string; + instagramUrl?: string; + discordUrl?: string; +}; +type Artwork = { + id: string; + name: string; + description: string; + sourceGame: string; + pathToMedia: string; + active: boolean; + createdAt: string; + contributors?: Contributor[]; +}; + +interface ArtworkPageProps { + artwork: Artwork; +} + +export default function ArtworkPage({ artwork }: ArtworkPageProps) { + return ( +
+
+ TODO add Header +
+
+
+ < Gallery +
+
+
+
+
+ + + +
+
+
+
+
+ {artwork.name} +
+
+
+ + {artwork.description} + +
+
+
+
+
+
+ Contributors +
+
+
+ {artwork.contributors?.map((contributor) => ( +
+
+ {contributor.name} +
+
+ {contributor.discordUrl ? ( +
+ + + +
+ ) : ( + "" + )} + {contributor.instagramUrl ? ( +
+ + + +
+ ) : ( + "" + )} +
+
+ ))} +
+
+
+
+
+
+ +
+
+ Game Image +
+
+
+ TODO add footer +
+
+ ); +} + +export const getServerSideProps: GetServerSideProps = async ( + context, +) => { + const { id } = context.params as { id: string }; + console.log("Fetching artwork with id:", id); + // const res = await fetch(`https://your-backend.com/api/artwork/${id}`); + const artwork: Artwork = { + id: "abc of art", + name: "title of art", + description: "description of art", + sourceGame: "", + pathToMedia: "", + active: false, + createdAt: new Date().toISOString(), + contributors: [ + { id: "1", name: "Contributor 1", discordUrl: "discordUrl" }, + { id: "2", name: "Contributor 2", instagramUrl: "instagramUrl" }, + ], + }; + + return { props: { artwork } }; +}; diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx new file mode 100644 index 00000000..b3e6d926 --- /dev/null +++ b/client/src/pages/artwork/index.tsx @@ -0,0 +1,131 @@ +import { GetServerSideProps } from "next"; +import Link from "next/link"; + +type Artwork = { + id: string; + name: string; + description: string; + sourceGame: string; + pathToMedia: string; + active: boolean; + createdAt: string; +}; + +interface ArtworksPageProps { + artworks: Artwork[]; +} + +const PLACEHOLDER_ICON = ( +
+ + + +
+); + +function renderArtworkCard(artwork: Artwork) { + return ( + +
+ {PLACEHOLDER_ICON} +
+ + ); +} + +export default function ArtworksPage({ artworks }: ArtworksPageProps) { + return ( +
+
+ TODO add Header +
+
+
+ FEATURED: +
+ SOME GAME +
+
+ {PLACEHOLDER_ICON} +
+
+
+
+ More about us → +
+
+
+
+ +
+
+ {artworks.map((artwork) => renderArtworkCard(artwork))} +
+
+
+ TODO add footer +
+
+ ); +} + +export const getServerSideProps: GetServerSideProps< + ArtworksPageProps +> = async () => { + // const res = await fetch(`https://your-backend.com/api/artwork/${id}`); + const artworks: Artwork[] = []; + for (let i = 0; i < 12; i++) { + const artwork: Artwork = { + id: i + "", + name: "title of art" + i, + description: "description of art", + sourceGame: "", + pathToMedia: "", + active: false, + createdAt: new Date().toISOString(), + }; + artworks.push(artwork); + } + + return { props: { artworks } }; +}; diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index 616c2b14..9079808f 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -33,6 +33,12 @@ --ring: 236 47% 7%; --radius: 0.5rem; + + --dark-2: #090A19; + --neutral-1: #1B1F4C; + --light-1: #FFFFFF; + --light-2: #CED1FE; + --light-3: #9CA4FD; } } @@ -41,3 +47,22 @@ @apply bg-background text-foreground; } } + +.bg-neutral-1 { + background-color: var(--neutral-1); +} +.bg-dark-2 { + background-color: var(--dark-2); +} +.bg-light-2 { + background-color: var(--light-2); +} +.text-light-1 { + color: var(--light-1); +} +.outline-neutral-1 { + outline-color: var(--neutral-1); +} +.text-light-3 { + color: var(--light-3); +} \ No newline at end of file From 7f91e0b507cce7ff90d4ece816fea4e81faa48b9 Mon Sep 17 00:00:00 2001 From: Karl_Sue Date: Sat, 6 Dec 2025 06:43:13 +0000 Subject: [PATCH 06/69] refactor: keep admin register simple --- server/game_dev/admin.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/server/game_dev/admin.py b/server/game_dev/admin.py index 2cfeab2e..0e8d557c 100644 --- a/server/game_dev/admin.py +++ b/server/game_dev/admin.py @@ -2,23 +2,8 @@ from .models import Member, Art, ArtContributor -@admin.register(Member) -class MemberAdmin(admin.ModelAdmin): - list_display = ['name', 'active', 'pronouns'] - list_filter = ['active'] - search_fields = ['name'] +admin.site.register(Member) +admin.site.register(Art) -@admin.register(Art) -class ArtAdmin(admin.ModelAdmin): - list_display = ['name', 'active'] - list_filter = ['active'] - search_fields = ['name'] - - -@admin.register(ArtContributor) -class ArtContributorAdmin(admin.ModelAdmin): - list_display = ['art', 'member', 'role'] - list_filter = ['role'] - search_fields = ['member__name', 'art__name'] - autocomplete_fields = ['art', 'member'] \ No newline at end of file +admin.site.register(ArtContributor) \ No newline at end of file From aeedeec5920eb87632fb57488d1d89d5934340dc Mon Sep 17 00:00:00 2001 From: Karl_Sue Date: Sat, 6 Dec 2025 06:44:01 +0000 Subject: [PATCH 07/69] fix: space error --- server/game_dev/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index ea132d6b..0735d3f3 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -22,14 +22,16 @@ class Art(models.Model): def __str__(self): return str(self.name) + class ArtContributor(models.Model): art = models.ForeignKey('Art', on_delete=models.CASCADE, related_name='contributors') member = models.ForeignKey('Member', on_delete=models.CASCADE, related_name='art_contributions') role = models.CharField(max_length=100) + class Meta: unique_together = ('art', 'member') verbose_name = 'Art Contributor' verbose_name_plural = 'Art Contributors' - + def __str__(self): return f"{self.member.name} - {self.art.name} ({self.role})" From 31954afefb1a9bb8180057441f2c0a19b95bdbb7 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Wed, 10 Dec 2025 10:57:58 +0800 Subject: [PATCH 08/69] Load arts from backend --- client/next.config.mjs | 10 +++ client/src/pages/artwork/[id].tsx | 96 ++++++++++------------------- client/src/pages/artwork/index.tsx | 48 ++++++--------- client/src/styles/globals.css | 3 + client/src/types/art-contributor.ts | 9 +++ client/src/types/art.ts | 8 +++ client/src/types/base-dto.ts | 3 + server/game_dev/serializers.py | 16 ++++- server/game_dev/urls.py | 4 +- server/game_dev/views.py | 14 ++++- 10 files changed, 113 insertions(+), 98 deletions(-) create mode 100644 client/src/types/art-contributor.ts create mode 100644 client/src/types/art.ts create mode 100644 client/src/types/base-dto.ts diff --git a/client/next.config.mjs b/client/next.config.mjs index c5b60fed..1402c100 100644 --- a/client/next.config.mjs +++ b/client/next.config.mjs @@ -19,6 +19,16 @@ const config = { // pollIntervalMs: 1000 // } // : undefined, + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: '**', + port: '', + pathname: '**' + } + ] + } }; export default config; diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 732f645b..ce89b26e 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -2,28 +2,19 @@ import { GetServerSideProps } from "next"; import Image from "next/image"; import Link from "next/link"; -type Contributor = { - id: string; - name: string; - instagramUrl?: string; - discordUrl?: string; -}; -type Artwork = { - id: string; - name: string; - description: string; - sourceGame: string; - pathToMedia: string; - active: boolean; - createdAt: string; - contributors?: Contributor[]; -}; +import api from "@/lib/api"; +import { Art } from "@/types/art"; +import { ArtContributor } from "@/types/art-contributor"; interface ArtworkPageProps { - artwork: Artwork; + artwork: Art; + contributors: ArtContributor[]; } -export default function ArtworkPage({ artwork }: ArtworkPageProps) { +export default function ArtworkPage({ + artwork, + contributors, +}: ArtworkPageProps) { return (
< Gallery
-
+
-
- - - +
+ Artwork image
- {artwork.contributors?.map((contributor) => ( + {contributors?.map((contributor) => (
- {contributor.name} + {contributor.member_name}
- {contributor.discordUrl ? ( + {contributor.discordUrl && (
- ) : ( - "" )} - {contributor.instagramUrl ? ( + {contributor.instagramUrl && (
- ) : ( - "" )}
@@ -205,21 +184,14 @@ export const getServerSideProps: GetServerSideProps = async ( context, ) => { const { id } = context.params as { id: string }; - console.log("Fetching artwork with id:", id); - // const res = await fetch(`https://your-backend.com/api/artwork/${id}`); - const artwork: Artwork = { - id: "abc of art", - name: "title of art", - description: "description of art", - sourceGame: "", - pathToMedia: "", - active: false, - createdAt: new Date().toISOString(), - contributors: [ - { id: "1", name: "Contributor 1", discordUrl: "discordUrl" }, - { id: "2", name: "Contributor 2", instagramUrl: "instagramUrl" }, - ], - }; - - return { props: { artwork } }; + const artResponse = await api.get(`game-dev/arts/${id}`); + const artwork = artResponse.data; + const contributorsResponse = await api.get( + `game-dev/art-contributors`, + ); + const contributors: ArtContributor[] = contributorsResponse.data.filter( + (x) => x.art_id === Number(id), + ); + // TODO [HanMinh] to filter on backend + return { props: { artwork, contributors } }; }; diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index b3e6d926..d0ca4af0 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -1,18 +1,12 @@ import { GetServerSideProps } from "next"; +import Image from "next/image"; import Link from "next/link"; -type Artwork = { - id: string; - name: string; - description: string; - sourceGame: string; - pathToMedia: string; - active: boolean; - createdAt: string; -}; +import api from "@/lib/api"; +import { Art } from "@/types/art"; interface ArtworksPageProps { - artworks: Artwork[]; + artworks: Art[]; } const PLACEHOLDER_ICON = ( @@ -32,18 +26,27 @@ const PLACEHOLDER_ICON = (
); -function renderArtworkCard(artwork: Artwork) { +function renderArtworkCard(artwork: Art) { return (
- {PLACEHOLDER_ICON} +
+ Artwork image +
); @@ -112,20 +115,7 @@ export default function ArtworksPage({ artworks }: ArtworksPageProps) { export const getServerSideProps: GetServerSideProps< ArtworksPageProps > = async () => { - // const res = await fetch(`https://your-backend.com/api/artwork/${id}`); - const artworks: Artwork[] = []; - for (let i = 0; i < 12; i++) { - const artwork: Artwork = { - id: i + "", - name: "title of art" + i, - description: "description of art", - sourceGame: "", - pathToMedia: "", - active: false, - createdAt: new Date().toISOString(), - }; - artworks.push(artwork); - } - - return { props: { artworks } }; + const res = await api.get("game-dev/arts"); + const arts = res.data; + return { props: { artworks: arts } }; }; diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index 9079808f..53eb51bc 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -65,4 +65,7 @@ } .text-light-3 { color: var(--light-3); +} +.border-light-2 { + border-color: var(--light-2); } \ No newline at end of file diff --git a/client/src/types/art-contributor.ts b/client/src/types/art-contributor.ts new file mode 100644 index 00000000..9ccc389f --- /dev/null +++ b/client/src/types/art-contributor.ts @@ -0,0 +1,9 @@ +import { BaseDto } from "./base-dto"; + +export interface ArtContributor extends BaseDto { + art_id: number; + member_name: string; + role: string; + instagramUrl?: string; // TODO [HanMinh] to refine where to get these info + discordUrl?: string; +} diff --git a/client/src/types/art.ts b/client/src/types/art.ts new file mode 100644 index 00000000..3587cbe4 --- /dev/null +++ b/client/src/types/art.ts @@ -0,0 +1,8 @@ +import { BaseDto } from "./base-dto"; + +export interface Art extends BaseDto { + name: string; + description: string; + path_to_media: string; + active: boolean; +} diff --git a/client/src/types/base-dto.ts b/client/src/types/base-dto.ts new file mode 100644 index 00000000..9e3b6872 --- /dev/null +++ b/client/src/types/base-dto.ts @@ -0,0 +1,3 @@ +export interface BaseDto { + id: number; +} diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index 768a4bfa..5c21cbc9 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -1,11 +1,21 @@ from rest_framework import serializers -from .models import ArtContributor +from .models import Art, ArtContributor, Member class ArtContributorSerializer(serializers.ModelSerializer): member_name = serializers.CharField(source='member.name', read_only=True) - art_name = serializers.CharField(source='art.name', read_only=True) + art_id = serializers.IntegerField(source="art.id", read_only=True) class Meta: model = ArtContributor - fields = ['id', 'art', 'member', 'member_name', 'art_name', 'role'] \ No newline at end of file + fields = ['id', 'art_id', 'member', 'member_name', 'role'] + +class ArtSerializer(serializers.ModelSerializer): + class Meta: + model = Art + fields = ['id', 'name', 'description', 'path_to_media', 'active'] + +class MemberSerializer(serializers.ModelSerializer): + class Meta: + model = Member + fields = ['name'] diff --git a/server/game_dev/urls.py b/server/game_dev/urls.py index d1799496..b6f94c27 100644 --- a/server/game_dev/urls.py +++ b/server/game_dev/urls.py @@ -1,9 +1,11 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from .views import ArtContributorViewSet +from .views import ArtContributorViewSet, ArtViewSet, MemberViewSet router = DefaultRouter() router.register(r'art-contributors', ArtContributorViewSet, basename='artcontributor') +router.register(r'arts', ArtViewSet, basename="art") +router.register(r'members', MemberViewSet, basename="member") urlpatterns = [ path('', include(router.urls)), diff --git a/server/game_dev/views.py b/server/game_dev/views.py index 264448f7..bf68916a 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -1,8 +1,16 @@ from rest_framework import viewsets -from .models import ArtContributor -from .serializers import ArtContributorSerializer +from .models import Art, ArtContributor, Member +from .serializers import ArtContributorSerializer, ArtSerializer, MemberSerializer class ArtContributorViewSet(viewsets.ModelViewSet): queryset = ArtContributor.objects.all() - serializer_class = ArtContributorSerializer \ No newline at end of file + serializer_class = ArtContributorSerializer + +class ArtViewSet(viewsets.ModelViewSet): + queryset = Art.objects.all() + serializer_class = ArtSerializer + +class MemberViewSet(viewsets.ModelViewSet): + queryset = Member.objects.all() + serializer_class = MemberSerializer \ No newline at end of file From 9e67fa6cb81bcf030663ae37f57b4eb81b7e18f4 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Wed, 10 Dec 2025 11:08:38 +0800 Subject: [PATCH 09/69] Change field name style --- client/src/pages/artwork/[id].tsx | 4 ++-- client/src/types/art-contributor.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index ce89b26e..a8f61707 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -102,7 +102,7 @@ export default function ArtworkPage({ {contributor.member_name}
- {contributor.discordUrl && ( + {contributor.discord_url && (
)} - {contributor.instagramUrl && ( + {contributor.instagram_url && (
Date: Wed, 10 Dec 2025 11:16:33 +0800 Subject: [PATCH 10/69] Readd art field to create data --- server/game_dev/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index 5c21cbc9..54c5c7a1 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -8,7 +8,7 @@ class ArtContributorSerializer(serializers.ModelSerializer): class Meta: model = ArtContributor - fields = ['id', 'art_id', 'member', 'member_name', 'role'] + fields = ['id', 'art', 'art_id', 'member', 'member_name', 'role'] class ArtSerializer(serializers.ModelSerializer): class Meta: From c10ee23476e2b299a6e94ee1035a7d23c01d918c Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Wed, 10 Dec 2025 13:48:46 +0800 Subject: [PATCH 11/69] Make the image height corresponding to the text --- client/src/pages/artwork/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index a8f61707..00527cde 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -40,7 +40,7 @@ export default function ArtworkPage({
Date: Wed, 10 Dec 2025 15:08:38 +0800 Subject: [PATCH 12/69] Filter on backend + include contributors into art --- client/src/pages/artwork/[id].tsx | 18 +++--------------- client/src/types/art.ts | 2 ++ server/api/settings.py | 1 + server/game_dev/serializers.py | 6 +++--- server/game_dev/views.py | 3 +++ 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 00527cde..0aa8f7c5 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -4,17 +4,12 @@ import Link from "next/link"; import api from "@/lib/api"; import { Art } from "@/types/art"; -import { ArtContributor } from "@/types/art-contributor"; interface ArtworkPageProps { artwork: Art; - contributors: ArtContributor[]; } -export default function ArtworkPage({ - artwork, - contributors, -}: ArtworkPageProps) { +export default function ArtworkPage({ artwork }: ArtworkPageProps) { return (
- {contributors?.map((contributor) => ( + {artwork.contributors?.map((contributor) => (
= async ( const { id } = context.params as { id: string }; const artResponse = await api.get(`game-dev/arts/${id}`); const artwork = artResponse.data; - const contributorsResponse = await api.get( - `game-dev/art-contributors`, - ); - const contributors: ArtContributor[] = contributorsResponse.data.filter( - (x) => x.art_id === Number(id), - ); - // TODO [HanMinh] to filter on backend - return { props: { artwork, contributors } }; + return { props: { artwork } }; }; diff --git a/client/src/types/art.ts b/client/src/types/art.ts index 3587cbe4..5a5397c7 100644 --- a/client/src/types/art.ts +++ b/client/src/types/art.ts @@ -1,3 +1,4 @@ +import { ArtContributor } from "./art-contributor"; import { BaseDto } from "./base-dto"; export interface Art extends BaseDto { @@ -5,4 +6,5 @@ export interface Art extends BaseDto { description: string; path_to_media: string; active: boolean; + contributors: ArtContributor[]; } diff --git a/server/api/settings.py b/server/api/settings.py index 424f34e5..f460957d 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -49,6 +49,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "django_extensions", + "django_filters", "rest_framework", "corsheaders", "api.healthcheck", diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index 54c5c7a1..48901998 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -4,16 +4,16 @@ class ArtContributorSerializer(serializers.ModelSerializer): member_name = serializers.CharField(source='member.name', read_only=True) - art_id = serializers.IntegerField(source="art.id", read_only=True) class Meta: model = ArtContributor - fields = ['id', 'art', 'art_id', 'member', 'member_name', 'role'] + fields = ['id', 'art', 'member', 'member_name', 'role'] class ArtSerializer(serializers.ModelSerializer): + contributors = ArtContributorSerializer(many = True, read_only = True) class Meta: model = Art - fields = ['id', 'name', 'description', 'path_to_media', 'active'] + fields = ['id', 'name', 'description', 'path_to_media', 'active', 'contributors'] class MemberSerializer(serializers.ModelSerializer): class Meta: diff --git a/server/game_dev/views.py b/server/game_dev/views.py index bf68916a..86d0f0ad 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -1,11 +1,14 @@ from rest_framework import viewsets from .models import Art, ArtContributor, Member from .serializers import ArtContributorSerializer, ArtSerializer, MemberSerializer +from django_filters.rest_framework import DjangoFilterBackend class ArtContributorViewSet(viewsets.ModelViewSet): queryset = ArtContributor.objects.all() serializer_class = ArtContributorSerializer + filter_backends = [DjangoFilterBackend] + filterset_fields=['art'] class ArtViewSet(viewsets.ModelViewSet): queryset = Art.objects.all() From 1f2ce0cca3b308c3af817f5a921c3c6a0fe6c239 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Wed, 10 Dec 2025 15:34:28 +0800 Subject: [PATCH 13/69] Add Pagination on backend --- client/src/pages/artwork/index.tsx | 13 +++++++------ client/src/types/page-response.ts | 6 ++++++ server/api/settings.py | 5 +++++ 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 client/src/types/page-response.ts diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index d0ca4af0..8a3586f5 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -4,9 +4,10 @@ import Link from "next/link"; import api from "@/lib/api"; import { Art } from "@/types/art"; +import { PageResult } from "@/types/page-response"; interface ArtworksPageProps { - artworks: Art[]; + pages: PageResult; } const PLACEHOLDER_ICON = ( @@ -52,7 +53,7 @@ function renderArtworkCard(artwork: Art) { ); } -export default function ArtworksPage({ artworks }: ArtworksPageProps) { +export default function ArtworksPage({ pages }: ArtworksPageProps) { return (
- {artworks.map((artwork) => renderArtworkCard(artwork))} + {pages.results.map((artwork) => renderArtworkCard(artwork))}
= async () => { - const res = await api.get("game-dev/arts"); - const arts = res.data; - return { props: { artworks: arts } }; + const res = await api.get>("game-dev/arts"); + const pages = res.data; + return { props: { pages } }; }; diff --git a/client/src/types/page-response.ts b/client/src/types/page-response.ts new file mode 100644 index 00000000..e5fa692e --- /dev/null +++ b/client/src/types/page-response.ts @@ -0,0 +1,6 @@ +export interface PageResult { + count: number; + next: string; + previous: string; + results: T[]; +} diff --git a/server/api/settings.py b/server/api/settings.py index f460957d..4664e69a 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -154,3 +154,8 @@ MEDIA_URL = "/media/" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +REST_FRAMEWORK = { + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 100, +} \ No newline at end of file From eed05cef3c3c0d0ef6e31362cede3ca87bf471ca Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Wed, 10 Dec 2025 18:02:26 +0800 Subject: [PATCH 14/69] Improve frontend to support responsive --- client/src/pages/artwork/[id].tsx | 208 +++++++++++++++++------------- 1 file changed, 115 insertions(+), 93 deletions(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 0aa8f7c5..b5c464d4 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -1,6 +1,7 @@ import { GetServerSideProps } from "next"; import Image from "next/image"; import Link from "next/link"; +import { JSX } from "react"; import api from "@/lib/api"; import { Art } from "@/types/art"; @@ -9,6 +10,84 @@ interface ArtworkPageProps { artwork: Art; } +const DISCORD_ICON = ( +
+ + + +
+); +const INSTAGRAM_ICON = ( +
+ + + +
+); + +function iconWithUrl(icon: JSX.Element, url: string) { + return {icon}; +} + +function displayContributors(artwork: Art) { + return ( +
+
+
+
+ Contributors +
+
+
+ {artwork.contributors?.map((contributor) => ( +
+
+ {contributor.member_name} +
+
+ {contributor.discord_url && + iconWithUrl(DISCORD_ICON, contributor.discord_url)} + {contributor.instagram_url && + iconWithUrl(INSTAGRAM_ICON, contributor.instagram_url)} +
+
+ ))} +
+
+
+ ); +} + export default function ArtworkPage({ artwork }: ArtworkPageProps) { return (
< Gallery
-
-
-
- Artwork image -
+
+
+ Artwork image
-
+
-
-
-
-
- Contributors -
-
-
- {artwork.contributors?.map((contributor) => ( -
-
- {contributor.member_name} -
-
- {contributor.discord_url && ( -
- - - -
- )} - {contributor.instagram_url && ( -
- - - -
- )} -
-
- ))} -
-
-
+ {displayContributors(artwork)} +
+
+
+
+
+ {artwork.name} +
+
+
+ + {artwork.description} +
+ {displayContributors(artwork)}
@@ -158,10 +180,10 @@ export default function ArtworkPage({ artwork }: ArtworkPageProps) { Game Image
From a1371d62d02753a709e2abf06569a94082d76bc1 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Wed, 10 Dec 2025 18:05:13 +0800 Subject: [PATCH 15/69] Improve resize image --- client/src/pages/artwork/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index b5c464d4..7cb0cea6 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -121,7 +121,7 @@ export default function ArtworkPage({ artwork }: ArtworkPageProps) { alt="Artwork image" width={500} height={500} - className="relative block sm:h-auto sm:max-w-full md:max-h-full md:w-auto" + className="relative block sm:h-auto sm:max-w-full md:max-h-full" />
Date: Wed, 10 Dec 2025 18:06:13 +0800 Subject: [PATCH 16/69] Back button padding --- client/src/pages/artwork/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 7cb0cea6..95adacb5 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -102,7 +102,7 @@ export default function ArtworkPage({ artwork }: ArtworkPageProps) {
Date: Sat, 13 Dec 2025 03:48:37 +0000 Subject: [PATCH 17/69] feature: improve responsive layout --- client/src/components/ui/goBack.tsx | 34 +++++++++++++++++++++++++ client/src/components/ui/imageFrame.tsx | 30 ++++++++++++++++++++++ client/src/pages/artwork/[id].tsx | 7 ++--- client/src/pages/artwork/index.tsx | 29 ++++++++++----------- 4 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 client/src/components/ui/goBack.tsx create mode 100644 client/src/components/ui/imageFrame.tsx diff --git a/client/src/components/ui/goBack.tsx b/client/src/components/ui/goBack.tsx new file mode 100644 index 00000000..4273aad5 --- /dev/null +++ b/client/src/components/ui/goBack.tsx @@ -0,0 +1,34 @@ +import Link from "next/link"; + +const ButtonGallery = () => { + return ( + + + + ); +}; + +export default ButtonGallery; diff --git a/client/src/components/ui/imageFrame.tsx b/client/src/components/ui/imageFrame.tsx new file mode 100644 index 00000000..6d66335c --- /dev/null +++ b/client/src/components/ui/imageFrame.tsx @@ -0,0 +1,30 @@ +import Image from "next/image"; +import React from "react"; + +interface CardProps { + imageSrc?: string; + imageAlt?: string; + children?: React.ReactNode; +} + +const Card = ({ imageSrc, imageAlt = "Artwork", children }: CardProps) => { + return ( +
+
+ {imageSrc ? ( + {imageAlt} + ) : ( + children || No Image + )} +
+
+ ); +}; + +export default Card; diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 732f645b..0afcd2a0 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -1,6 +1,7 @@ import { GetServerSideProps } from "next"; import Image from "next/image"; -import Link from "next/link"; + +import ButtonGallery from "@/components/ui/goBack"; type Contributor = { id: string; @@ -37,13 +38,13 @@ export default function ArtworkPage({ artwork }: ArtworkPageProps) {
- < Gallery +
-
- {PLACEHOLDER_ICON} -
+ + {!artwork.pathToMedia && PLACEHOLDER_ICON} + ); } @@ -80,17 +81,13 @@ export default function ArtworksPage({ artworks }: ArtworksPageProps) { data-layer="Auto Layout Horizontal" className="AutoLayoutHorizontal items-start justify-start gap-6" > -
-
- More about us → -
-
+ More about us → +
From 3b5607e4d186fccf1bb89f3ca52dff7f79470932 Mon Sep 17 00:00:00 2001 From: Karl_Sue Date: Sat, 13 Dec 2025 05:03:41 +0000 Subject: [PATCH 18/69] feat: add Art hook --- client/src/hooks/useArtworkData.ts | 18 ++++++++++++++++++ client/src/pages/artwork/index.tsx | 20 ++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 client/src/hooks/useArtworkData.ts diff --git a/client/src/hooks/useArtworkData.ts b/client/src/hooks/useArtworkData.ts new file mode 100644 index 00000000..1008d07e --- /dev/null +++ b/client/src/hooks/useArtworkData.ts @@ -0,0 +1,18 @@ +import { Art } from "@/types/art"; + +export const generateMockArtworks = (count: number): Art[] => { + const artworks: Art[] = []; + for (let i = 1; i <= count; i++) { + artworks.push({ + id: i, + name: `Artwork ${i}`, + description: "Mock artwork description", + //source_game: "Mock Game", + path_to_media: "", + active: true, + contributors: [], + //created_at: new Date().toISOString(), + }); + } + return artworks; +}; diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index f5f7d8c9..e34fbddf 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { Button } from "@/components/ui/button"; import Card from "@/components/ui/imageFrame"; +import { generateMockArtworks } from "@/hooks/useArtworkData"; import api from "@/lib/api"; import { Art } from "@/types/art"; import { PageResult } from "@/types/page-response"; @@ -112,13 +113,24 @@ export const getServerSideProps: GetServerSideProps< const res = await api.get>("game-dev/arts"); return { props: { artworks: res.data } }; } catch { + // return { + // props: { + // artworks: { + // count: 0, + // next: null as unknown as string, + // previous: null as unknown as string, + // results: [] as Art[], + // }, + // }, + // }; ==> use when successfully populate db + const mockArtworks = generateMockArtworks(12); return { props: { artworks: { - count: 0, - next: null as unknown as string, - previous: null as unknown as string, - results: [] as Art[], + count: mockArtworks.length, + next: "", + previous: "", + results: mockArtworks, }, }, }; From 2e9ef677e925232df5d78aa1b114074db132e620 Mon Sep 17 00:00:00 2001 From: Karl_Sue Date: Sat, 13 Dec 2025 05:18:23 +0000 Subject: [PATCH 19/69] feat: add Artwork hook --- client/src/hooks/useArtworkData.ts | 31 ++++++++++++++++++++++++++++++ client/src/pages/artwork/[id].tsx | 13 ++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/client/src/hooks/useArtworkData.ts b/client/src/hooks/useArtworkData.ts index 1008d07e..eca1ceee 100644 --- a/client/src/hooks/useArtworkData.ts +++ b/client/src/hooks/useArtworkData.ts @@ -16,3 +16,34 @@ export const generateMockArtworks = (count: number): Art[] => { } return artworks; }; + +export const generateMockArtwork = (id: string): Art => { + return { + id: Number(id), + name: "Mock Artwork Title", + description: + "Lorem ipsum dolor sit amet. Non numquam dicta nam autem dicta 33 error molestias et repellat consequatur eum iste expedita est dolorem libero et quas provident!", + //source_game: "Mock Game", + path_to_media: "/placeholder1293x405.svg", + active: true, + //created_at: new Date().toISOString(), + contributors: [ + { + id: 1, + art_id: Number(id), + member_name: "Contributor 1", + role: "user1", + discord_url: "https://discord.com", + instagram_url: "", + }, + { + id: 2, + art_id: Number(id), + member_name: "Contributor 2", + role: "user2", + discord_url: "", + instagram_url: "https://instagram.com", + }, + ], + }; +}; diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index d37ad25e..15d1681a 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -3,6 +3,7 @@ import Image from "next/image"; import { JSX } from "react"; import ButtonGallery from "@/components/ui/goBack"; +import { generateMockArtwork } from "@/hooks/useArtworkData"; import api from "@/lib/api"; import { Art } from "@/types/art"; @@ -201,7 +202,13 @@ export const getServerSideProps: GetServerSideProps = async ( context, ) => { const { id } = context.params as { id: string }; - const artResponse = await api.get(`game-dev/arts/${id}`); - const artwork = artResponse.data; - return { props: { artwork } }; + try { + const artResponse = await api.get(`game-dev/arts/${id}`); + const artwork = artResponse.data; + return { props: { artwork } }; + } catch { + // Return mock data when API fails or DB is empty + const mockArtwork = generateMockArtwork(id); + return { props: { artwork: mockArtwork } }; + } }; From 3d3811954caf12ae5b08247ec8dcc6e1c764cad3 Mon Sep 17 00:00:00 2001 From: Karl_Sue Date: Sat, 13 Dec 2025 05:31:21 +0000 Subject: [PATCH 20/69] feat: add placeholder art --- client/src/hooks/useArtworkData.ts | 2 +- client/src/pages/artwork/[id].tsx | 37 ++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/client/src/hooks/useArtworkData.ts b/client/src/hooks/useArtworkData.ts index eca1ceee..44d0136c 100644 --- a/client/src/hooks/useArtworkData.ts +++ b/client/src/hooks/useArtworkData.ts @@ -24,7 +24,7 @@ export const generateMockArtwork = (id: string): Art => { description: "Lorem ipsum dolor sit amet. Non numquam dicta nam autem dicta 33 error molestias et repellat consequatur eum iste expedita est dolorem libero et quas provident!", //source_game: "Mock Game", - path_to_media: "/placeholder1293x405.svg", + path_to_media: "", active: true, //created_at: new Date().toISOString(), contributors: [ diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 15d1681a..ac8ecc3a 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -117,13 +117,36 @@ export default function ArtworkPage({ artwork }: ArtworkPageProps) { className="Frame1099 bg-neutral-1 justify-start md:flex" >
- Artwork image + {artwork.path_to_media ? ( + Artwork image + ) : ( + // in case fail to load image or no image in db yet +
+
+ + + +
+
+ )}
Date: Sat, 13 Dec 2025 14:42:47 +0800 Subject: [PATCH 21/69] Fix flake8 --- server/api/settings.py | 2 +- server/api/urls.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/api/settings.py b/server/api/settings.py index 4664e69a..46902758 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -158,4 +158,4 @@ REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 100, -} \ No newline at end of file +} diff --git a/server/api/urls.py b/server/api/urls.py index c6a16187..073e0e7d 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -21,5 +21,5 @@ urlpatterns = [ path("admin/", admin.site.urls), path("api/healthcheck/", include("api.healthcheck.urls")), - path("api/game-dev/", include("game_dev.urls")), -] \ No newline at end of file + path("api/game-dev/", include("game_dev.urls")), +] From 45afd6d96391e76b11db3c46e1368afc04eee624 Mon Sep 17 00:00:00 2001 From: Karl_Sue Date: Sat, 13 Dec 2025 06:52:52 +0000 Subject: [PATCH 22/69] fix: match Prettier code style --- client/src/styles/globals.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index 53eb51bc..43a74218 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -34,11 +34,11 @@ --radius: 0.5rem; - --dark-2: #090A19; - --neutral-1: #1B1F4C; - --light-1: #FFFFFF; - --light-2: #CED1FE; - --light-3: #9CA4FD; + --dark-2: #090a19; + --neutral-1: #1b1f4c; + --light-1: #ffffff; + --light-2: #ced1fe; + --light-3: #9ca4fd; } } @@ -68,4 +68,4 @@ } .border-light-2 { border-color: var(--light-2); -} \ No newline at end of file +} From b7273d5751968658f4d1df9179e0cb66a630ba28 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 13 Dec 2025 14:54:42 +0800 Subject: [PATCH 23/69] Fix flake8 --- server/game_dev/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/game_dev/admin.py b/server/game_dev/admin.py index 0e8d557c..b148cace 100644 --- a/server/game_dev/admin.py +++ b/server/game_dev/admin.py @@ -6,4 +6,4 @@ admin.site.register(Art) -admin.site.register(ArtContributor) \ No newline at end of file +admin.site.register(ArtContributor) From 53e30c3b3b279672e711d93f24faf62e4013e2b6 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 18:18:56 +0800 Subject: [PATCH 24/69] Refactor code for reuseability --- .../ui/{goBack.tsx => go-back-button.tsx} | 12 +++++--- .../ui/{imageFrame.tsx => image-card.tsx} | 6 ++-- .../src/components/ui/image-placeholder.tsx | 26 +++++++++++++++++ ...{useArtworkData.ts => use-artwork-data.ts} | 0 client/src/pages/artwork/[id].tsx | 28 ++++--------------- client/src/pages/artwork/index.tsx | 18 +++--------- 6 files changed, 46 insertions(+), 44 deletions(-) rename client/src/components/ui/{goBack.tsx => go-back-button.tsx} (79%) rename client/src/components/ui/{imageFrame.tsx => image-card.tsx} (87%) create mode 100644 client/src/components/ui/image-placeholder.tsx rename client/src/hooks/{useArtworkData.ts => use-artwork-data.ts} (100%) diff --git a/client/src/components/ui/goBack.tsx b/client/src/components/ui/go-back-button.tsx similarity index 79% rename from client/src/components/ui/goBack.tsx rename to client/src/components/ui/go-back-button.tsx index 4273aad5..22109a4a 100644 --- a/client/src/components/ui/goBack.tsx +++ b/client/src/components/ui/go-back-button.tsx @@ -1,8 +1,12 @@ import Link from "next/link"; -const ButtonGallery = () => { +interface GoBackButtonProps { + url: string; + label: string; +} +const GoBackButton = ({ url, label }: GoBackButtonProps) => { return ( - +
-

Gallery

+

{label}

); }; -export default ButtonGallery; +export default GoBackButton; diff --git a/client/src/components/ui/imageFrame.tsx b/client/src/components/ui/image-card.tsx similarity index 87% rename from client/src/components/ui/imageFrame.tsx rename to client/src/components/ui/image-card.tsx index 6d66335c..f3ca51b1 100644 --- a/client/src/components/ui/imageFrame.tsx +++ b/client/src/components/ui/image-card.tsx @@ -1,13 +1,13 @@ import Image from "next/image"; import React from "react"; -interface CardProps { +interface ImageCard { imageSrc?: string; imageAlt?: string; children?: React.ReactNode; } -const Card = ({ imageSrc, imageAlt = "Artwork", children }: CardProps) => { +const ImageCard = ({ imageSrc, imageAlt = "Image", children }: ImageCard) => { return (
@@ -27,4 +27,4 @@ const Card = ({ imageSrc, imageAlt = "Artwork", children }: CardProps) => { ); }; -export default Card; +export default ImageCard; diff --git a/client/src/components/ui/image-placeholder.tsx b/client/src/components/ui/image-placeholder.tsx new file mode 100644 index 00000000..b7e25e58 --- /dev/null +++ b/client/src/components/ui/image-placeholder.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +const ImagePlaceholder = () => { + return ( +
+
+ + + +
+
+ ); +}; +export default ImagePlaceholder; diff --git a/client/src/hooks/useArtworkData.ts b/client/src/hooks/use-artwork-data.ts similarity index 100% rename from client/src/hooks/useArtworkData.ts rename to client/src/hooks/use-artwork-data.ts diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index ac8ecc3a..8e3b89b7 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -2,8 +2,9 @@ import { GetServerSideProps } from "next"; import Image from "next/image"; import { JSX } from "react"; -import ButtonGallery from "@/components/ui/goBack"; -import { generateMockArtwork } from "@/hooks/useArtworkData"; +import GoBackButton from "@/components/ui/go-back-button"; +import ImagePlaceholder from "@/components/ui/image-placeholder"; +import { generateMockArtwork } from "@/hooks/use-artwork-data"; import api from "@/lib/api"; import { Art } from "@/types/art"; @@ -109,7 +110,7 @@ export default function ArtworkPage({ artwork }: ArtworkPageProps) { data-layer="< Gallery" className="Gallery text-light-1 h-10 justify-start font-['DM_Sans'] text-3xl font-bold leading-10 tracking-tight" > - +
) : ( - // in case fail to load image or no image in db yet -
-
- - - -
-
+ )}
- {!artwork.path_to_media && PLACEHOLDER_ICON} - + ); } @@ -113,16 +113,6 @@ export const getServerSideProps: GetServerSideProps< const res = await api.get>("game-dev/arts"); return { props: { artworks: res.data } }; } catch { - // return { - // props: { - // artworks: { - // count: 0, - // next: null as unknown as string, - // previous: null as unknown as string, - // results: [] as Art[], - // }, - // }, - // }; ==> use when successfully populate db const mockArtworks = generateMockArtworks(12); return { props: { From b234e606c3f33f683df289d1edf6bcf77cad7420 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:18:58 +0800 Subject: [PATCH 25/69] Error message --- .../src/components/ui/modal/error-modal.tsx | 45 +++++++++++++++++++ client/src/pages/artwork/[id].tsx | 34 +++++++------- client/src/pages/artwork/index.tsx | 30 +++++-------- client/src/styles/globals.css | 4 ++ 4 files changed, 80 insertions(+), 33 deletions(-) create mode 100644 client/src/components/ui/modal/error-modal.tsx diff --git a/client/src/components/ui/modal/error-modal.tsx b/client/src/components/ui/modal/error-modal.tsx new file mode 100644 index 00000000..d5ff49db --- /dev/null +++ b/client/src/components/ui/modal/error-modal.tsx @@ -0,0 +1,45 @@ +import React, { useState } from "react"; + +interface ErrorModalProps { + message: string | null; + onClose: () => void; +} + +const ErrorModal = ({ message, onClose = () => {} }: ErrorModalProps) => { + const [isVisible, setIsVisible] = useState(true); + if (!isVisible || !message) { + return null; + } + + function onModalClose() { + setIsVisible(false); + onClose(); + } + + return ( + // Backdrop overlay +
+ {/* Modal content container */} +
e.stopPropagation()} // Prevent closing when clicking inside the modal + > +

Error

+

{message}

+
+ +
+
+
+ ); +}; + +export default ErrorModal; diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 8e3b89b7..29c6a369 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -1,15 +1,17 @@ import { GetServerSideProps } from "next"; import Image from "next/image"; +import { useRouter } from "next/navigation"; import { JSX } from "react"; import GoBackButton from "@/components/ui/go-back-button"; import ImagePlaceholder from "@/components/ui/image-placeholder"; -import { generateMockArtwork } from "@/hooks/use-artwork-data"; +import ErrorModal from "@/components/ui/modal/error-modal"; import api from "@/lib/api"; import { Art } from "@/types/art"; interface ArtworkPageProps { - artwork: Art; + artwork?: Art; + error?: string; } const DISCORD_ICON = ( @@ -90,7 +92,11 @@ function displayContributors(artwork: Art) { ); } -export default function ArtworkPage({ artwork }: ArtworkPageProps) { +export default function ArtworkPage({ artwork, error }: ArtworkPageProps) { + const router = useRouter(); + if (error) { + return router.back()} />; + } return (
- {artwork.path_to_media ? ( + {artwork!.path_to_media ? ( Artwork image - {artwork.name} + {artwork!.name}
- {artwork.description} + {artwork!.description}
- {displayContributors(artwork)} + {displayContributors(artwork!)}
@@ -163,7 +169,7 @@ export default function ArtworkPage({ artwork }: ArtworkPageProps) { data-layer="Art Name" className="ArtName text-light-3 flex justify-center font-['Jersey_10'] text-8xl font-normal leading-[76px] tracking-wide" > - {artwork.name} + {artwork!.name}
- {artwork.description} + {artwork!.description}
- {displayContributors(artwork)} + {displayContributors(artwork!)}
@@ -211,9 +217,7 @@ export const getServerSideProps: GetServerSideProps = async ( const artResponse = await api.get(`game-dev/arts/${id}`); const artwork = artResponse.data; return { props: { artwork } }; - } catch { - // Return mock data when API fails or DB is empty - const mockArtwork = generateMockArtwork(id); - return { props: { artwork: mockArtwork } }; + } catch (err: { message: string }) { + return { props: { error: err.message || "Failed to load artwork." } }; } }; diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index ab5542d6..6388ccd9 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -1,15 +1,17 @@ import { GetServerSideProps } from "next"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import ImageCard from "@/components/ui/image-card"; -import { generateMockArtworks } from "@/hooks/use-artwork-data"; +import ErrorModal from "@/components/ui/modal/error-modal"; import api from "@/lib/api"; import { Art } from "@/types/art"; import { PageResult } from "@/types/page-response"; interface ArtworksPageProps { - artworks: PageResult; + artworks?: PageResult; + error?: string; } const PLACEHOLDER_ICON = ( @@ -48,7 +50,11 @@ function renderArtworkCard(artwork: Art) { ); } -export default function ArtworksPage({ artworks }: ArtworksPageProps) { +export default function ArtworksPage({ artworks, error }: ArtworksPageProps) { + const router = useRouter(); + if (error) { + return router.back()} />; + } return (
- {artworks.results.map((artwork) => renderArtworkCard(artwork))} + {artworks!.results.map((artwork) => renderArtworkCard(artwork))}
= async () => { try { const res = await api.get>("game-dev/arts"); return { props: { artworks: res.data } }; - } catch { - const mockArtworks = generateMockArtworks(12); - return { - props: { - artworks: { - count: mockArtworks.length, - next: "", - previous: "", - results: mockArtworks, - }, - }, - }; + } catch (err: { message: string }) { + return { props: { error: err.message || "Failed to load artworks." } }; } }; diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index 43a74218..81287554 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -39,6 +39,7 @@ --light-1: #ffffff; --light-2: #ced1fe; --light-3: #9ca4fd; + --error: #fa5c5c; } } @@ -69,3 +70,6 @@ .border-light-2 { border-color: var(--light-2); } +.bg-error { + background-color: var(--error); +} \ No newline at end of file From cc4ac457527c9a56558344a1772d7c4b45a21b85 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:22:47 +0800 Subject: [PATCH 26/69] Remove mock data on Frontend --- client/src/hooks/use-artwork-data.ts | 49 ---------------------------- 1 file changed, 49 deletions(-) delete mode 100644 client/src/hooks/use-artwork-data.ts diff --git a/client/src/hooks/use-artwork-data.ts b/client/src/hooks/use-artwork-data.ts deleted file mode 100644 index 44d0136c..00000000 --- a/client/src/hooks/use-artwork-data.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Art } from "@/types/art"; - -export const generateMockArtworks = (count: number): Art[] => { - const artworks: Art[] = []; - for (let i = 1; i <= count; i++) { - artworks.push({ - id: i, - name: `Artwork ${i}`, - description: "Mock artwork description", - //source_game: "Mock Game", - path_to_media: "", - active: true, - contributors: [], - //created_at: new Date().toISOString(), - }); - } - return artworks; -}; - -export const generateMockArtwork = (id: string): Art => { - return { - id: Number(id), - name: "Mock Artwork Title", - description: - "Lorem ipsum dolor sit amet. Non numquam dicta nam autem dicta 33 error molestias et repellat consequatur eum iste expedita est dolorem libero et quas provident!", - //source_game: "Mock Game", - path_to_media: "", - active: true, - //created_at: new Date().toISOString(), - contributors: [ - { - id: 1, - art_id: Number(id), - member_name: "Contributor 1", - role: "user1", - discord_url: "https://discord.com", - instagram_url: "", - }, - { - id: 2, - art_id: Number(id), - member_name: "Contributor 2", - role: "user2", - discord_url: "", - instagram_url: "https://instagram.com", - }, - ], - }; -}; From f0c5e4cd4e3f01bdb6e8a64bfb5cd48a3ab39eba Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:37:40 +0800 Subject: [PATCH 27/69] Solve conflict and adapt code --- client/src/components/main/Navbar.tsx | 2 +- client/src/pages/artwork/[id].tsx | 8 +------- client/src/pages/artwork/index.tsx | 8 +------- server/game_dev/admin.py | 2 +- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/client/src/components/main/Navbar.tsx b/client/src/components/main/Navbar.tsx index b25a62b1..67a0dbef 100644 --- a/client/src/components/main/Navbar.tsx +++ b/client/src/components/main/Navbar.tsx @@ -18,7 +18,7 @@ export default function Navbar() { return ( <> -
+
-
- TODO add Header -
= async ( ) => { const { id } = context.params as { id: string }; try { - const artResponse = await api.get(`game-dev/arts/${id}`); + const artResponse = await api.get(`arts/${id}`); const artwork = artResponse.data; return { props: { artwork } }; } catch (err: { message: string }) { diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index 6388ccd9..a29a78d4 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -57,12 +57,6 @@ export default function ArtworksPage({ artworks, error }: ArtworksPageProps) { } return (
-
- TODO add Header -
= async () => { try { - const res = await api.get>("game-dev/arts"); + const res = await api.get>("arts"); return { props: { artworks: res.data } }; } catch (err: { message: string }) { return { props: { error: err.message || "Failed to load artworks." } }; diff --git a/server/game_dev/admin.py b/server/game_dev/admin.py index 58e968dd..c2d8a5e2 100644 --- a/server/game_dev/admin.py +++ b/server/game_dev/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import Member, Event +from .models import Art, ArtContributor, Member, Event class MemberAdmin(admin.ModelAdmin): From 08f0865b2b3a32515e39569db9c2ff4323513c5e Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:41:04 +0800 Subject: [PATCH 28/69] fix flake8 on backend --- server/game_dev/serializers.py | 1 + server/game_dev/views.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index 659373c2..d9173974 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -15,6 +15,7 @@ class Meta: "location", ] + class ArtContributorSerializer(serializers.ModelSerializer): member_name = serializers.CharField(source='member.name', read_only=True) diff --git a/server/game_dev/views.py b/server/game_dev/views.py index 70e5bfb9..313093f6 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -18,7 +18,6 @@ def get_queryset(self): return Event.objects.filter(id=self.kwargs["id"]) - class ArtContributorViewSet(viewsets.ModelViewSet): queryset = ArtContributor.objects.all() serializer_class = ArtContributorSerializer @@ -33,4 +32,4 @@ class ArtViewSet(viewsets.ModelViewSet): class MemberViewSet(viewsets.ModelViewSet): queryset = Member.objects.all() - serializer_class = MemberSerializer \ No newline at end of file + serializer_class = MemberSerializer From 23b65ea0fa38a3aa92fe5db5fc9c30d5bdb44a11 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:43:12 +0800 Subject: [PATCH 29/69] Fix Prettier and type check --- client/src/pages/artwork/[id].tsx | 6 ++++-- client/src/pages/artwork/index.tsx | 6 ++++-- client/src/styles/globals.css | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index cf0f0cad..f61b3ed9 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -211,7 +211,9 @@ export const getServerSideProps: GetServerSideProps = async ( const artResponse = await api.get(`arts/${id}`); const artwork = artResponse.data; return { props: { artwork } }; - } catch (err: { message: string }) { - return { props: { error: err.message || "Failed to load artwork." } }; + } catch (err: unknown) { + return { + props: { error: (err as Error).message || "Failed to load artwork." }, + }; } }; diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index a29a78d4..4211949a 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -110,7 +110,9 @@ export const getServerSideProps: GetServerSideProps< try { const res = await api.get>("arts"); return { props: { artworks: res.data } }; - } catch (err: { message: string }) { - return { props: { error: err.message || "Failed to load artworks." } }; + } catch (err: unknown) { + return { + props: { error: (err as Error).message || "Failed to load artworks." }, + }; } }; diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index 0836bc29..8cf2f38f 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -91,4 +91,4 @@ } .bg-error { background-color: var(--error); -} \ No newline at end of file +} From 6cc9d1c677bab95f63e803275bd2b7fb1351f1f4 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:49:32 +0800 Subject: [PATCH 30/69] Correct django-filter version --- server/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/pyproject.toml b/server/pyproject.toml index dd0e67ee..fb713c85 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -16,7 +16,7 @@ gunicorn = "^23.0.0" python-dotenv = "^1.0.1" django-extensions = "^3.2.3" pillow = "^11.3.0" -django-filter = "^25.2" +django-filter = "^24.3" [tool.poetry.group.dev.dependencies] From 39306efc944e4af5e0c5fcf0f5d12e53fb38114c Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:54:06 +0800 Subject: [PATCH 31/69] Commit poetry.lock --- server/poetry.lock | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/server/poetry.lock b/server/poetry.lock index 282ace1b..aab552a0 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -91,20 +91,17 @@ Django = ">=3.2" [[package]] name = "django-filter" -version = "25.2" +version = "24.3" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." optional = false -python-versions = ">=3.10" +python-versions = ">=3.8" files = [ - {file = "django_filter-25.2-py3-none-any.whl", hash = "sha256:9c0f8609057309bba611062fe1b720b4a873652541192d232dd28970383633e3"}, - {file = "django_filter-25.2.tar.gz", hash = "sha256:760e984a931f4468d096f5541787efb8998c61217b73006163bf2f9523fe8f23"}, + {file = "django_filter-24.3-py3-none-any.whl", hash = "sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64"}, + {file = "django_filter-24.3.tar.gz", hash = "sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3"}, ] [package.dependencies] -Django = ">=5.2" - -[package.extras] -drf = ["djangorestframework"] +Django = ">=4.2" [[package]] name = "djangorestframework" From d564d057bcda81823441b37177ea7b2d150d2fee Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:55:22 +0800 Subject: [PATCH 32/69] Commit poetry.lock --- server/poetry.lock | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/server/poetry.lock b/server/poetry.lock index aab552a0..202b5e94 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "asgiref" @@ -6,6 +6,7 @@ version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, @@ -20,6 +21,7 @@ version = "2.15.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" +groups = ["dev"] files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, @@ -35,6 +37,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -46,13 +50,14 @@ version = "5.1.15" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "django-5.1.15-py3-none-any.whl", hash = "sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432"}, {file = "django-5.1.15.tar.gz", hash = "sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947"}, ] [package.dependencies] -asgiref = ">=3.8.1" +asgiref = ">=3.8.1,<4" sqlparse = ">=0.3.1" tzdata = {version = "*", markers = "sys_platform == \"win32\""} @@ -66,6 +71,7 @@ version = "4.4.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django_cors_headers-4.4.0-py3-none-any.whl", hash = "sha256:5c6e3b7fe870876a1efdfeb4f433782c3524078fa0dc9e0195f6706ce7a242f6"}, {file = "django_cors_headers-4.4.0.tar.gz", hash = "sha256:92cf4633e22af67a230a1456cb1b7a02bb213d6536d2dcb2a4a24092ea9cebc2"}, @@ -81,6 +87,7 @@ version = "3.2.3" description = "Extensions for Django" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a"}, {file = "django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401"}, @@ -95,6 +102,7 @@ version = "24.3" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django_filter-24.3-py3-none-any.whl", hash = "sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64"}, {file = "django_filter-24.3.tar.gz", hash = "sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3"}, @@ -109,6 +117,7 @@ version = "3.15.2" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"}, {file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"}, @@ -123,6 +132,7 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -139,6 +149,7 @@ version = "1.4" description = "Plugin to catch bad style specific to Django Projects." optional = false python-versions = ">=3.7.2,<4.0.0" +groups = ["dev"] files = [ {file = "flake8_django-1.4.tar.gz", hash = "sha256:4debba883084191568e3187416d1d6bdd4abd826da988f197a3c36572e9f30de"}, ] @@ -153,6 +164,7 @@ version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -167,6 +179,7 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -188,6 +201,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -199,6 +213,7 @@ version = "1.10.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, @@ -245,6 +260,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -256,6 +272,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -267,6 +284,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -382,7 +400,7 @@ fpx = ["olefile"] mic = ["olefile"] test-arrow = ["pyarrow"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -391,6 +409,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -406,6 +425,7 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -417,6 +437,7 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -428,6 +449,7 @@ version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, @@ -448,6 +470,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -462,6 +485,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -476,6 +500,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -487,6 +512,7 @@ version = "0.5.1" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, @@ -502,6 +528,8 @@ version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, @@ -513,6 +541,7 @@ version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, @@ -587,6 +616,6 @@ files = [ ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" -content-hash = "c5308f28a61ca954f8c322f9d9c4ae9412b82c10eb7a262c8ee438554aeed16f" +content-hash = "3056497732593ecab7864ba1cc8d96295741aeeafedf53d57c757311f6a7e009" From b0a3062be540534cf5695ceb1860d558511e4404 Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:57:16 +0800 Subject: [PATCH 33/69] Correct script order --- .../{0002_art_artcontributor.py => 0005_art_artcontributor.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename server/game_dev/migrations/{0002_art_artcontributor.py => 0005_art_artcontributor.py} (100%) diff --git a/server/game_dev/migrations/0002_art_artcontributor.py b/server/game_dev/migrations/0005_art_artcontributor.py similarity index 100% rename from server/game_dev/migrations/0002_art_artcontributor.py rename to server/game_dev/migrations/0005_art_artcontributor.py From d1c5ee9df6631245b7d867566f4ddcfefcec17ec Mon Sep 17 00:00:00 2001 From: Han Minh Tran Date: Sat, 20 Dec 2025 19:58:50 +0800 Subject: [PATCH 34/69] Correct script order --- server/game_dev/migrations/0005_art_artcontributor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/game_dev/migrations/0005_art_artcontributor.py b/server/game_dev/migrations/0005_art_artcontributor.py index 138685d9..f3d0c905 100644 --- a/server/game_dev/migrations/0005_art_artcontributor.py +++ b/server/game_dev/migrations/0005_art_artcontributor.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ("game_dev", "0001_initial"), + ("game_dev", "0004_alter_event_date"), ] operations = [ From c820c7d2c48890c9d3608f4121896173c67d9d6b Mon Sep 17 00:00:00 2001 From: David <24074639@student.uwa.edu.au> Date: Tue, 6 Jan 2026 19:42:18 +0000 Subject: [PATCH 35/69] feat: implement individual art pages with full functionality - Changed Art model from CharField to ImageField for media - Added ImageField with upload_to='art_images/' - Created migration 0006 for field rename - Added discord_url and instagram_url to ArtContributor model - Created migration 0007 for social media fields - Updated serializers for both Art and ArtContributor models - Updated TypeScript types for Art model - Added artwork index and detail pages with dynamic routing - Replaced hardcoded SVG icons with react-social-icons library - Configured Next.js to allow localhost images - Fixed duplicate color definitions in globals.css - Updated Tailwind config for consistent HSL colors - Created test data script with sample images and social links --- client/next.config.mjs | 33 +++------ client/src/pages/artwork/[id].tsx | 70 ++++++------------- client/src/pages/artwork/index.tsx | 7 +- client/src/styles/globals.css | 9 +-- client/src/types/art.ts | 2 +- client/tailwind.config.ts | 2 + .../0006_rename_path_to_media_to_media.py | 25 +++++++ ...0007_add_social_links_to_artcontributor.py | 23 ++++++ server/game_dev/models.py | 4 +- server/game_dev/serializers.py | 4 +- 10 files changed, 91 insertions(+), 88 deletions(-) create mode 100644 server/game_dev/migrations/0006_rename_path_to_media_to_media.py create mode 100644 server/game_dev/migrations/0007_add_social_links_to_artcontributor.py diff --git a/client/next.config.mjs b/client/next.config.mjs index 28739846..c8a0f711 100644 --- a/client/next.config.mjs +++ b/client/next.config.mjs @@ -1,12 +1,6 @@ -// import os from "node:os"; -// import isInsideContainer from "is-inside-container"; - -// const isWindowsDevContainer = () => -// os.release().toLowerCase().includes("microsoft") && isInsideContainer(); - /** @type {import('next').NextConfig} */ -const config = { +const nextConfig = { reactStrictMode: true, turbopack: { root: import.meta.dirname, @@ -14,24 +8,15 @@ const config = { outputFileTracingRoot: import.meta.dirname, images: { domains: ["localhost"], - }, - // Turns on file change polling for the Windows Dev Container - // Doesn't work currently for turbopack, so file changes will not automatically update the client. - // watchOptions: isWindowsDevContainer() - // ? { - // pollIntervalMs: 1000 - // } - // : undefined, - images: { remotePatterns: [ { - protocol: 'https', - hostname: '**', - port: '', - pathname: '**' - } - ] - } + protocol: 'http', + hostname: 'localhost', + port: '8000', + pathname: '/media/**', + }, + ], + }, }; -export default config; +export default nextConfig; diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index f61b3ed9..5fa25927 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -1,7 +1,7 @@ import { GetServerSideProps } from "next"; import Image from "next/image"; import { useRouter } from "next/navigation"; -import { JSX } from "react"; +import { SocialIcon } from "react-social-icons"; import GoBackButton from "@/components/ui/go-back-button"; import ImagePlaceholder from "@/components/ui/image-placeholder"; @@ -14,46 +14,6 @@ interface ArtworkPageProps { error?: string; } -const DISCORD_ICON = ( -
- - - -
-); -const INSTAGRAM_ICON = ( -
- - - -
-); - -function iconWithUrl(icon: JSX.Element, url: string) { - return {icon}; -} - function displayContributors(artwork: Art) { return (
@@ -78,11 +38,25 @@ function displayContributors(artwork: Art) {
{contributor.member_name}
-
- {contributor.discord_url && - iconWithUrl(DISCORD_ICON, contributor.discord_url)} - {contributor.instagram_url && - iconWithUrl(INSTAGRAM_ICON, contributor.instagram_url)} +
+ {contributor.discord_url && ( + + )} + {contributor.instagram_url && ( + + )}
))} @@ -118,9 +92,9 @@ export default function ArtworkPage({ artwork, error }: ArtworkPageProps) { className="Frame1099 bg-neutral-1 justify-start md:flex" >
- {artwork!.path_to_media ? ( + {artwork!.media ? ( Artwork image - - {!artwork.path_to_media && PLACEHOLDER_ICON} + + {!artwork.media && PLACEHOLDER_ICON} ); diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index 8cf2f38f..dd23d87d 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -16,12 +16,14 @@ --neutral-5: hsl(203 82% 18%); --light-1: hsl(0 0% 100%); --light-2: hsl(236 18% 98%); + --light-3: hsl(236 96% 80%); /* light purple */ --light-alt: hsl(260 46% 90%); /* light green */ --light-alt-2: hsl(183 100% 79%); --logo-blue-2: hsl(237 66% 77%); --logo-blue-1: hsl(236 62% 95%); + --error: hsl(0 93% 67%); /* Colours to be used for components */ --background: 236 47% 7%; @@ -52,13 +54,6 @@ --input: 235 47% 20%; --ring: 236 47% 7%; --radius: 0.5rem; - - --dark-2: #090a19; - --neutral-1: #1b1f4c; - --light-1: #ffffff; - --light-2: #ced1fe; - --light-3: #9ca4fd; - --error: #fa5c5c; } } diff --git a/client/src/types/art.ts b/client/src/types/art.ts index 5a5397c7..f00c2979 100644 --- a/client/src/types/art.ts +++ b/client/src/types/art.ts @@ -4,7 +4,7 @@ import { BaseDto } from "./base-dto"; export interface Art extends BaseDto { name: string; description: string; - path_to_media: string; + media: string; active: boolean; contributors: ArtContributor[]; } diff --git a/client/tailwind.config.ts b/client/tailwind.config.ts index 915464d8..d6a49201 100644 --- a/client/tailwind.config.ts +++ b/client/tailwind.config.ts @@ -69,10 +69,12 @@ const config = { neutral_5: "var(--neutral-5)", light_1: "var(--light-1)", light_2: "var(--light-2)", + light_3: "var(--light-3)", light_alt: "var(--light-alt)", light_alt_2: "var(--light-alt-2)", logo_blue_2: "var(--logo-blue-2)", logo_blue_1: "var(--logo-blue-1)", + error: "var(--error)", }, borderRadius: { lg: "var(--radius)", diff --git a/server/game_dev/migrations/0006_rename_path_to_media_to_media.py b/server/game_dev/migrations/0006_rename_path_to_media_to_media.py new file mode 100644 index 00000000..9b9b54ee --- /dev/null +++ b/server/game_dev/migrations/0006_rename_path_to_media_to_media.py @@ -0,0 +1,25 @@ +# Generated manually for changing path_to_media to media (ImageField) + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0005_art_artcontributor"), + ] + + operations = [ + # First, rename the field + migrations.RenameField( + model_name="art", + old_name="path_to_media", + new_name="media", + ), + # Then, alter the field to ImageField + migrations.AlterField( + model_name="art", + name="media", + field=models.ImageField(upload_to='art_images/'), + ), + ] diff --git a/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py b/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py new file mode 100644 index 00000000..e3e8cba4 --- /dev/null +++ b/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py @@ -0,0 +1,23 @@ +# Generated manually for adding discord_url and instagram_url to ArtContributor + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0006_rename_path_to_media_to_media"), + ] + + operations = [ + migrations.AddField( + model_name="artcontributor", + name="discord_url", + field=models.URLField(max_length=500, blank=True, null=True), + ), + migrations.AddField( + model_name="artcontributor", + name="instagram_url", + field=models.URLField(max_length=500, blank=True, null=True), + ), + ] diff --git a/server/game_dev/models.py b/server/game_dev/models.py index abf0ff87..6bd3aa43 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -28,7 +28,7 @@ class Art(models.Model): name = models.CharField(null=False, max_length=200) description = models.CharField(max_length=200,) # source_game = models.ForeignKey(Games, on_delete=models.CASCADE, related_name='art_pieces') #Need implement Games model - path_to_media = models.CharField(null=False, max_length=500) + media = models.ImageField(upload_to='art_images/', null=False) active = models.BooleanField(null=False) def __str__(self): @@ -39,6 +39,8 @@ class ArtContributor(models.Model): art = models.ForeignKey('Art', on_delete=models.CASCADE, related_name='contributors') member = models.ForeignKey('Member', on_delete=models.CASCADE, related_name='art_contributions') role = models.CharField(max_length=100) + discord_url = models.URLField(max_length=500, blank=True, null=True) + instagram_url = models.URLField(max_length=500, blank=True, null=True) class Meta: unique_together = ('art', 'member') diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index d9173974..aa0ed057 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -21,7 +21,7 @@ class ArtContributorSerializer(serializers.ModelSerializer): class Meta: model = ArtContributor - fields = ['id', 'art', 'member', 'member_name', 'role'] + fields = ['id', 'art', 'member', 'member_name', 'role', 'discord_url', 'instagram_url'] class ArtSerializer(serializers.ModelSerializer): @@ -29,7 +29,7 @@ class ArtSerializer(serializers.ModelSerializer): class Meta: model = Art - fields = ['id', 'name', 'description', 'path_to_media', 'active', 'contributors'] + fields = ['id', 'name', 'description', 'media', 'active', 'contributors'] class MemberSerializer(serializers.ModelSerializer): From 21121714e1c5ffe068ed89257311dfa91253f0bb Mon Sep 17 00:00:00 2001 From: David <24074639@student.uwa.edu.au> Date: Tue, 6 Jan 2026 19:58:54 +0000 Subject: [PATCH 36/69] fix: remove null=True from URLField to pass flake8 - Changed discord_url and instagram_url to use blank=True with default='' - Removed null=True to follow Django best practices for string fields - Updated migration 0007 accordingly --- .../migrations/0007_add_social_links_to_artcontributor.py | 4 ++-- server/game_dev/models.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py b/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py index e3e8cba4..a7949306 100644 --- a/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py +++ b/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py @@ -13,11 +13,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name="artcontributor", name="discord_url", - field=models.URLField(max_length=500, blank=True, null=True), + field=models.URLField(max_length=500, blank=True, default=''), ), migrations.AddField( model_name="artcontributor", name="instagram_url", - field=models.URLField(max_length=500, blank=True, null=True), + field=models.URLField(max_length=500, blank=True, default=''), ), ] diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 6bd3aa43..d2cf4d79 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -39,8 +39,8 @@ class ArtContributor(models.Model): art = models.ForeignKey('Art', on_delete=models.CASCADE, related_name='contributors') member = models.ForeignKey('Member', on_delete=models.CASCADE, related_name='art_contributions') role = models.CharField(max_length=100) - discord_url = models.URLField(max_length=500, blank=True, null=True) - instagram_url = models.URLField(max_length=500, blank=True, null=True) + discord_url = models.URLField(max_length=500, blank=True, default='') + instagram_url = models.URLField(max_length=500, blank=True, default='') class Meta: unique_together = ('art', 'member') From 6640e7fdbfb1b0bb66b70d1cdb6d021ece57019b Mon Sep 17 00:00:00 2001 From: David <24074639@student.uwa.edu.au> Date: Tue, 6 Jan 2026 20:03:06 +0000 Subject: [PATCH 37/69] style: fix prettier formatting in error-modal.tsx --- client/src/components/ui/modal/error-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/ui/modal/error-modal.tsx b/client/src/components/ui/modal/error-modal.tsx index d5ff49db..ba1c2404 100644 --- a/client/src/components/ui/modal/error-modal.tsx +++ b/client/src/components/ui/modal/error-modal.tsx @@ -31,7 +31,7 @@ const ErrorModal = ({ message, onClose = () => {} }: ErrorModalProps) => {

{message}

{contributor.discord_url && ( - + className="flex h-[30px] w-[30px] items-center justify-center rounded-full bg-[#9CA4FD] transition-opacity hover:opacity-80" + > + + )} {contributor.instagram_url && ( - + className="flex h-[30px] w-[30px] items-center justify-center rounded-full bg-[#9CA4FD] transition-opacity hover:opacity-80" + > + + )}
From 4277b4dd95b91c0cd0d4866aa6af66b88c66e4d3 Mon Sep 17 00:00:00 2001 From: Peitong Du <101039613+DDuu123321@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:54:03 +0800 Subject: [PATCH 39/69] Fix import formatting in [id].tsx --- client/src/pages/artwork/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 941cf373..abcf31bd 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -1,4 +1,4 @@ -import { Instagram,MessageSquare } from "lucide-react"; +import { Instagram, MessageSquare } from "lucide-react"; import { GetServerSideProps } from "next"; import Image from "next/image"; import { useRouter } from "next/navigation"; From 959c3e18d0dcc7b13a8c30d4b85864955c30fb5c Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Fri, 9 Jan 2026 08:56:54 +0000 Subject: [PATCH 40/69] feature: add mock data for dev --- client/src/hooks/use-artwork-data.ts | 49 ++++++++++++++++++++++++++++ client/src/pages/artwork/index.tsx | 19 +++++++++-- 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 client/src/hooks/use-artwork-data.ts diff --git a/client/src/hooks/use-artwork-data.ts b/client/src/hooks/use-artwork-data.ts new file mode 100644 index 00000000..da08c9e9 --- /dev/null +++ b/client/src/hooks/use-artwork-data.ts @@ -0,0 +1,49 @@ +import { Art } from "@/types/art"; + +export const generateMockArtworks = (count: number): Art[] => { + const artworks: Art[] = []; + for (let i = 1; i <= count; i++) { + artworks.push({ + id: i, + name: `Artwork ${i}`, + description: "Mock artwork description", + //source_game: "Mock Game", + media: "", + active: true, + contributors: [], + //created_at: new Date().toISOString(), + }); + } + return artworks; +}; + +export const generateMockArtwork = (id: string): Art => { + return { + id: Number(id), + name: "Mock Artwork Title", + description: + "Lorem ipsum dolor sit amet. Non numquam dicta nam autem dicta 33 error molestias et repellat consequatur eum iste expedita est dolorem libero et quas provident!", + //source_game: "Mock Game", + media: "", + active: true, + //created_at: new Date().toISOString(), + contributors: [ + { + id: 1, + art_id: Number(id), + member_name: "Contributor 1", + role: "user1", + discord_url: "https://discord.com", + instagram_url: "", + }, + { + id: 2, + art_id: Number(id), + member_name: "Contributor 2", + role: "user2", + discord_url: "", + instagram_url: "https://instagram.com", + }, + ], + }; +}; diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index 1f355e74..61b04b30 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import ImageCard from "@/components/ui/image-card"; import ErrorModal from "@/components/ui/modal/error-modal"; +import { generateMockArtworks } from "@/hooks/use-artwork-data"; import api from "@/lib/api"; import { Art } from "@/types/art"; import { PageResult } from "@/types/page-response"; @@ -107,9 +108,23 @@ export const getServerSideProps: GetServerSideProps< try { const res = await api.get>("arts"); return { props: { artworks: res.data } }; - } catch (err: unknown) { + //} catch (err: unknown) { + } catch { + // return { + // props: { error: (err as Error).message || "Failed to load artworks." }, + // }; + + // Fallback to mock data on error + const mockArtworks = generateMockArtworks(12); return { - props: { error: (err as Error).message || "Failed to load artworks." }, + props: { + artworks: { + results: mockArtworks, + count: mockArtworks.length, + next: "", + previous: "", + }, + }, }; } }; From 6a7a5e03554ef7a1779da5b0b45c8e118262d2f3 Mon Sep 17 00:00:00 2001 From: David <24074639@student.uwa.edu.au> Date: Fri, 9 Jan 2026 16:08:19 +0000 Subject: [PATCH 41/69] chore: update migrations and frontend components - Updated migration files formatting - Updated artwork pages and components - Updated go-back-button component - Updated styles and Tailwind config - Updated serializers --- client/src/components/ui/go-back-button.tsx | 4 +- client/src/pages/artwork/[id].tsx | 54 ++++++++++--------- client/src/pages/artwork/index.tsx | 4 +- client/src/styles/globals.css | 25 --------- client/tailwind.config.ts | 3 +- .../0006_rename_path_to_media_to_media.py | 2 - ...0007_add_social_links_to_artcontributor.py | 2 - server/game_dev/serializers.py | 3 +- 8 files changed, 38 insertions(+), 59 deletions(-) diff --git a/client/src/components/ui/go-back-button.tsx b/client/src/components/ui/go-back-button.tsx index 22109a4a..5f1ebce1 100644 --- a/client/src/components/ui/go-back-button.tsx +++ b/client/src/components/ui/go-back-button.tsx @@ -8,10 +8,10 @@ const GoBackButton = ({ url, label }: GoBackButtonProps) => { return ( + + +
diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index dd23d87d..db50e92e 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -87,3 +87,21 @@ .bg-error { background-color: var(--error); } + +@layer utilities { + @keyframes float { + 0% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } + 100% { + transform: translateY(0); + } + } + + .animate-float { + animation: float 3s infinite; + } +} \ No newline at end of file From 4c5c1ba9f4a5e89506e1516f3913751056d23751 Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Wed, 14 Jan 2026 09:39:35 +0000 Subject: [PATCH 44/69] Refactor: Move social links from ArtContributor to Member model and remove django-filter dependency --- server/api/settings.py | 1 - server/game_dev/models.py | 12 +++++++----- server/game_dev/urls.py | 5 ++--- server/pyproject.toml | 1 - 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/server/api/settings.py b/server/api/settings.py index 46902758..8b0ee42c 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -49,7 +49,6 @@ "django.contrib.messages", "django.contrib.staticfiles", "django_extensions", - "django_filters", "rest_framework", "corsheaders", "api.healthcheck", diff --git a/server/game_dev/models.py b/server/game_dev/models.py index d2cf4d79..76b91288 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -7,6 +7,8 @@ class Member(models.Model): profile_picture = models.ImageField(upload_to="profiles/", null=True) about = models.CharField(max_length=256, blank=True) pronouns = models.CharField(max_length=20, blank=True) + discord_url = models.URLField(max_length=500, blank=True, null=True) + instagram_url = models.URLField(max_length=500, blank=True, null=True) def __str__(self): return str(self.name) @@ -28,8 +30,8 @@ class Art(models.Model): name = models.CharField(null=False, max_length=200) description = models.CharField(max_length=200,) # source_game = models.ForeignKey(Games, on_delete=models.CASCADE, related_name='art_pieces') #Need implement Games model - media = models.ImageField(upload_to='art_images/', null=False) - active = models.BooleanField(null=False) + media = models.ImageField(upload_to='art/', null=False) + active = models.BooleanField(default=True) def __str__(self): return str(self.name) @@ -39,11 +41,11 @@ class ArtContributor(models.Model): art = models.ForeignKey('Art', on_delete=models.CASCADE, related_name='contributors') member = models.ForeignKey('Member', on_delete=models.CASCADE, related_name='art_contributions') role = models.CharField(max_length=100) - discord_url = models.URLField(max_length=500, blank=True, default='') - instagram_url = models.URLField(max_length=500, blank=True, default='') class Meta: - unique_together = ('art', 'member') + constraints = [ + models.UniqueConstraint(fields=['art', 'member'], name='unique_art_member') + ] verbose_name = 'Art Contributor' verbose_name_plural = 'Art Contributors' diff --git a/server/game_dev/urls.py b/server/game_dev/urls.py index 58559694..dadcd6dd 100644 --- a/server/game_dev/urls.py +++ b/server/game_dev/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import path from rest_framework.routers import DefaultRouter from .views import EventDetailAPIView, ArtContributorViewSet, ArtViewSet, MemberViewSet @@ -9,5 +9,4 @@ urlpatterns = [ path("events//", EventDetailAPIView.as_view()), - path('', include(router.urls)), -] +] + router.urls diff --git a/server/pyproject.toml b/server/pyproject.toml index fb713c85..5ec547cb 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -16,7 +16,6 @@ gunicorn = "^23.0.0" python-dotenv = "^1.0.1" django-extensions = "^3.2.3" pillow = "^11.3.0" -django-filter = "^24.3" [tool.poetry.group.dev.dependencies] From 2951ead1ad24bca4718c66be5900e6aeff4bb85a Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Fri, 16 Jan 2026 15:29:45 +0000 Subject: [PATCH 45/69] Fix: Remove hardcoded social media fields to comply with schema design - Remove discord_url and instagram_url from Member model - Remove social media fields from ArtContributor model - Delete incorrect migration 0007 (social links on ArtContributor) - Update ArtContributorSerializer to remove social media field references - Change unique_together to UniqueConstraint (modern Django approach) - Add default=True to Art.active field - Keep ArtContributor as pure junction table (art, member, role only) This change aligns with the original schema design where social media links are managed through a separate SocialMedia table (in development on another branch). Addresses James's code review feedback. --- ...0007_add_social_links_to_artcontributor.py | 21 -------- ...artcontributor_unique_together_and_more.py | 33 ++++++++++++ server/game_dev/models.py | 2 - server/game_dev/serializers.py | 2 +- server/poetry.lock | 51 ++----------------- 5 files changed, 38 insertions(+), 71 deletions(-) delete mode 100644 server/game_dev/migrations/0007_add_social_links_to_artcontributor.py create mode 100644 server/game_dev/migrations/0007_alter_artcontributor_unique_together_and_more.py diff --git a/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py b/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py deleted file mode 100644 index 9656e5bc..00000000 --- a/server/game_dev/migrations/0007_add_social_links_to_artcontributor.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("game_dev", "0006_rename_path_to_media_to_media"), - ] - - operations = [ - migrations.AddField( - model_name="artcontributor", - name="discord_url", - field=models.URLField(max_length=500, blank=True, default=''), - ), - migrations.AddField( - model_name="artcontributor", - name="instagram_url", - field=models.URLField(max_length=500, blank=True, default=''), - ), - ] diff --git a/server/game_dev/migrations/0007_alter_artcontributor_unique_together_and_more.py b/server/game_dev/migrations/0007_alter_artcontributor_unique_together_and_more.py new file mode 100644 index 00000000..3c917f6b --- /dev/null +++ b/server/game_dev/migrations/0007_alter_artcontributor_unique_together_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.1.15 on 2026-01-16 15:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0006_rename_path_to_media_to_media"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="artcontributor", + unique_together=set(), + ), + migrations.AlterField( + model_name="art", + name="active", + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name="art", + name="media", + field=models.ImageField(upload_to="art/"), + ), + migrations.AddConstraint( + model_name="artcontributor", + constraint=models.UniqueConstraint( + fields=("art", "member"), name="unique_art_member" + ), + ), + ] diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 76b91288..58c2e1fe 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -7,8 +7,6 @@ class Member(models.Model): profile_picture = models.ImageField(upload_to="profiles/", null=True) about = models.CharField(max_length=256, blank=True) pronouns = models.CharField(max_length=20, blank=True) - discord_url = models.URLField(max_length=500, blank=True, null=True) - instagram_url = models.URLField(max_length=500, blank=True, null=True) def __str__(self): return str(self.name) diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index fc58e490..dd12bcb5 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -22,7 +22,7 @@ class ArtContributorSerializer(serializers.ModelSerializer): class Meta: model = ArtContributor - fields = ['id', 'art_id', 'member', 'member_name', 'role', 'discord_url', 'instagram_url'] + fields = ['id', 'art_id', 'member', 'member_name', 'role'] class ArtSerializer(serializers.ModelSerializer): diff --git a/server/poetry.lock b/server/poetry.lock index 202b5e94..8e6deac4 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "asgiref" @@ -6,7 +6,6 @@ version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, @@ -21,7 +20,6 @@ version = "2.15.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" -groups = ["dev"] files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, @@ -37,8 +35,6 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -50,7 +46,6 @@ version = "5.1.15" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" -groups = ["main"] files = [ {file = "django-5.1.15-py3-none-any.whl", hash = "sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432"}, {file = "django-5.1.15.tar.gz", hash = "sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947"}, @@ -71,7 +66,6 @@ version = "4.4.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "django_cors_headers-4.4.0-py3-none-any.whl", hash = "sha256:5c6e3b7fe870876a1efdfeb4f433782c3524078fa0dc9e0195f6706ce7a242f6"}, {file = "django_cors_headers-4.4.0.tar.gz", hash = "sha256:92cf4633e22af67a230a1456cb1b7a02bb213d6536d2dcb2a4a24092ea9cebc2"}, @@ -87,7 +81,6 @@ version = "3.2.3" description = "Extensions for Django" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a"}, {file = "django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401"}, @@ -96,28 +89,12 @@ files = [ [package.dependencies] Django = ">=3.2" -[[package]] -name = "django-filter" -version = "24.3" -description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "django_filter-24.3-py3-none-any.whl", hash = "sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64"}, - {file = "django_filter-24.3.tar.gz", hash = "sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3"}, -] - -[package.dependencies] -Django = ">=4.2" - [[package]] name = "djangorestframework" version = "3.15.2" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"}, {file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"}, @@ -132,7 +109,6 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" -groups = ["dev"] files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -149,7 +125,6 @@ version = "1.4" description = "Plugin to catch bad style specific to Django Projects." optional = false python-versions = ">=3.7.2,<4.0.0" -groups = ["dev"] files = [ {file = "flake8_django-1.4.tar.gz", hash = "sha256:4debba883084191568e3187416d1d6bdd4abd826da988f197a3c36572e9f30de"}, ] @@ -164,7 +139,6 @@ version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -179,7 +153,6 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -201,7 +174,6 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -213,7 +185,6 @@ version = "1.10.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, @@ -260,7 +231,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -272,7 +242,6 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -284,7 +253,6 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -400,7 +368,7 @@ fpx = ["olefile"] mic = ["olefile"] test-arrow = ["pyarrow"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions ; python_version < \"3.10\""] +typing = ["typing-extensions"] xmp = ["defusedxml"] [[package]] @@ -409,7 +377,6 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -425,7 +392,6 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -437,7 +403,6 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -449,7 +414,6 @@ version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, @@ -470,7 +434,6 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -485,7 +448,6 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -500,7 +462,6 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -512,7 +473,6 @@ version = "0.5.1" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, @@ -528,8 +488,6 @@ version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" -groups = ["main"] -markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, @@ -541,7 +499,6 @@ version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, @@ -616,6 +573,6 @@ files = [ ] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = "^3.12" -content-hash = "3056497732593ecab7864ba1cc8d96295741aeeafedf53d57c757311f6a7e009" +content-hash = "f804c2f3998772b91e34ad214e5fcafe900bec97675f73046d3bcc79aba0f7db" From 1138ccb0f01766001928bd14e844196f7fa65c4d Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Fri, 16 Jan 2026 15:36:49 +0000 Subject: [PATCH 46/69] Resolve merge conflict in Navbar: keep z-100 from main --- client/src/components/main/Navbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/main/Navbar.tsx b/client/src/components/main/Navbar.tsx index 67a0dbef..8567f937 100644 --- a/client/src/components/main/Navbar.tsx +++ b/client/src/components/main/Navbar.tsx @@ -18,7 +18,7 @@ export default function Navbar() { return ( <> -
+
Date: Fri, 16 Jan 2026 15:41:11 +0000 Subject: [PATCH 47/69] Fix: Remove django_filters import and usage from views.py --- server/game_dev/views.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/server/game_dev/views.py b/server/game_dev/views.py index 313093f6..9aa13adb 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -1,10 +1,6 @@ -# from django.shortcuts import render - -# Create your views here. from rest_framework import generics, viewsets from .models import Event, Art, ArtContributor, Member from .serializers import EventSerializer, ArtContributorSerializer, ArtSerializer, MemberSerializer -from django_filters.rest_framework import DjangoFilterBackend class EventDetailAPIView(generics.RetrieveAPIView): @@ -21,8 +17,6 @@ def get_queryset(self): class ArtContributorViewSet(viewsets.ModelViewSet): queryset = ArtContributor.objects.all() serializer_class = ArtContributorSerializer - filter_backends = [DjangoFilterBackend] - filterset_fields = ['art'] class ArtViewSet(viewsets.ModelViewSet): @@ -32,4 +26,4 @@ class ArtViewSet(viewsets.ModelViewSet): class MemberViewSet(viewsets.ModelViewSet): queryset = Member.objects.all() - serializer_class = MemberSerializer + serializer_class = MemberSerializer \ No newline at end of file From 8e9de276ac599b7b1e0987fb23538075f30f3c18 Mon Sep 17 00:00:00 2001 From: Peitong Du <101039613+DDuu123321@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:43:29 +0800 Subject: [PATCH 48/69] Update views.py --- server/game_dev/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/game_dev/views.py b/server/game_dev/views.py index 9aa13adb..cea2a84d 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -26,4 +26,5 @@ class ArtViewSet(viewsets.ModelViewSet): class MemberViewSet(viewsets.ModelViewSet): queryset = Member.objects.all() - serializer_class = MemberSerializer \ No newline at end of file + serializer_class = MemberSerializer + From 85ea95d8125e8b1e1f57fbd6a2ca792d2d44bb11 Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Fri, 16 Jan 2026 16:00:45 +0000 Subject: [PATCH 49/69] Fix: Add newline at end of views.py --- server/game_dev/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/game_dev/views.py b/server/game_dev/views.py index cea2a84d..e4693138 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -1,3 +1,6 @@ +# from django.shortcuts import render + +# Create your views here. from rest_framework import generics, viewsets from .models import Event, Art, ArtContributor, Member from .serializers import EventSerializer, ArtContributorSerializer, ArtSerializer, MemberSerializer @@ -27,4 +30,3 @@ class ArtViewSet(viewsets.ModelViewSet): class MemberViewSet(viewsets.ModelViewSet): queryset = Member.objects.all() serializer_class = MemberSerializer - From 163cc05634ffa95b79ac4ef116fcd5c4ae03929f Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Sat, 17 Jan 2026 02:55:09 +0000 Subject: [PATCH 50/69] refactor: remove pagination --- server/api/settings.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/api/settings.py b/server/api/settings.py index 8b0ee42c..424f34e5 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -153,8 +153,3 @@ MEDIA_URL = "/media/" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -REST_FRAMEWORK = { - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - 'PAGE_SIZE': 100, -} From 57a7a844fe0a76e331f7af907afd644bf4b1f6aa Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Sat, 17 Jan 2026 03:03:23 +0000 Subject: [PATCH 51/69] refactor: change from viewset to a standard RetrieveAPIView --- server/game_dev/views.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/server/game_dev/views.py b/server/game_dev/views.py index e4693138..d38423ce 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -1,9 +1,9 @@ # from django.shortcuts import render # Create your views here. -from rest_framework import generics, viewsets -from .models import Event, Art, ArtContributor, Member -from .serializers import EventSerializer, ArtContributorSerializer, ArtSerializer, MemberSerializer +from rest_framework import generics +from .models import Event, Art +from .serializers import EventSerializer, ArtSerializer class EventDetailAPIView(generics.RetrieveAPIView): @@ -17,16 +17,12 @@ def get_queryset(self): return Event.objects.filter(id=self.kwargs["id"]) -class ArtContributorViewSet(viewsets.ModelViewSet): - queryset = ArtContributor.objects.all() - serializer_class = ArtContributorSerializer - - -class ArtViewSet(viewsets.ModelViewSet): - queryset = Art.objects.all() +class ArtDetailAPIView(generics.RetrieveAPIView): + """ + GET /api/artworks// + """ serializer_class = ArtSerializer + lookup_url_kwarg = "id" - -class MemberViewSet(viewsets.ModelViewSet): - queryset = Member.objects.all() - serializer_class = MemberSerializer + def get_queryset(self): + return Art.objects.filter(id=self.kwargs["id"]) From 752ed72626330d6a55865cdc62f72d4b525c285d Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Sat, 17 Jan 2026 03:07:38 +0000 Subject: [PATCH 52/69] bug: remove leftover code: --- server/game_dev/urls.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/game_dev/urls.py b/server/game_dev/urls.py index dadcd6dd..9fb36b62 100644 --- a/server/game_dev/urls.py +++ b/server/game_dev/urls.py @@ -1,11 +1,8 @@ from django.urls import path from rest_framework.routers import DefaultRouter -from .views import EventDetailAPIView, ArtContributorViewSet, ArtViewSet, MemberViewSet +from .views import EventDetailAPIView router = DefaultRouter() -router.register(r'art-contributors', ArtContributorViewSet, basename='artcontributor') -router.register(r'arts', ArtViewSet, basename="art") -router.register(r'members', MemberViewSet, basename="member") urlpatterns = [ path("events//", EventDetailAPIView.as_view()), From 2a1cea8cd939b0558f4cf96999d7b6431843c91f Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Sat, 17 Jan 2026 03:59:19 +0000 Subject: [PATCH 53/69] refactor: remove redundant file --- client/src/components/ui/featureArt.tsx | 90 ------------------------- 1 file changed, 90 deletions(-) delete mode 100644 client/src/components/ui/featureArt.tsx diff --git a/client/src/components/ui/featureArt.tsx b/client/src/components/ui/featureArt.tsx deleted file mode 100644 index e8417cb0..00000000 --- a/client/src/components/ui/featureArt.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import Image from "next/image"; -import React from "react"; - -interface FeatureArtProps { - title?: string; - category?: string; - imageSrc?: string; - imageAlt?: string; -} - -export default function FeatureArt({ - title = "Featured Artwork", - category = "Digital Art", - imageSrc, - imageAlt = "Featured artwork", -}: FeatureArtProps) { - return ( -
- {/* Image */} -
- {imageSrc ? ( - {imageAlt} - ) : ( - - - - - - - - - - - - - - - - )} -
- - {/* Text Content */} -
-

{title}

- {category} -
-
- ); -} From 7afaaa9122f98199c9390eb0831ffd3e5ac82663 Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Sat, 17 Jan 2026 04:20:30 +0000 Subject: [PATCH 54/69] bug: fix the unused components error --- client/src/pages/artwork/index.tsx | 52 +++++------------------------- 1 file changed, 8 insertions(+), 44 deletions(-) diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index 4b765b7e..0a4b3427 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -2,7 +2,6 @@ import { GetServerSideProps } from "next"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { Button } from "@/components/ui/button"; import ImageCard from "@/components/ui/image-card"; import ErrorModal from "@/components/ui/modal/error-modal"; import { generateMockArtworks } from "@/hooks/use-artwork-data"; @@ -53,53 +52,18 @@ export default function ArtworksPage({ artworks, error }: ArtworksPageProps) { if (error) { return router.back()} />; } + return ( -
-
-
+
+
+

FEATURED -
- SOME GAME -

-
- {PLACEHOLDER_ICON} -
-
- - - -
-
+ -
-
- {artworks!.results.map((artwork) => renderArtworkCard(artwork))} +
+ {artworks?.results.map(renderArtworkCard)}
-
-
- TODO add footer -
+
); } From 665ac3d6ba2c678848e4dd356b8c212e44ba4f4b Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Wed, 21 Jan 2026 10:52:57 +0000 Subject: [PATCH 55/69] refactor: redesign artshowcase --- client/src/components/ui/image-card.tsx | 60 +++++++++++---- client/src/pages/artwork/index.tsx | 98 ++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 18 deletions(-) diff --git a/client/src/components/ui/image-card.tsx b/client/src/components/ui/image-card.tsx index f3ca51b1..31353bf2 100644 --- a/client/src/components/ui/image-card.tsx +++ b/client/src/components/ui/image-card.tsx @@ -1,26 +1,58 @@ import Image from "next/image"; import React from "react"; -interface ImageCard { +interface ImageCardProps { imageSrc?: string; imageAlt?: string; + /** Optional content rendered on the front (over the image or placeholder). */ children?: React.ReactNode; + /** Optional content rendered on the back when hovering/focused. */ + backContent?: React.ReactNode; } -const ImageCard = ({ imageSrc, imageAlt = "Image", children }: ImageCard) => { +const ImageCard = ({ + imageSrc, + imageAlt = "Image", + children, + backContent, +}: ImageCardProps) => { + const hasBack = Boolean(backContent); + const cardFlipClass = hasBack + ? " group-hover:[transform:rotateY(180deg)]" + : ""; + return ( -
-
- {imageSrc ? ( - {imageAlt} - ) : ( - children || No Image +
+
+
+ {imageSrc ? ( + <> + {imageAlt} + {children && ( +
+ {children} +
+ )} + + ) : ( +
+ {children || No Image} +
+ )} +
+ + {hasBack && ( +
+ {backContent} +
)}
diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index 0a4b3427..9bdfb3aa 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -40,7 +40,94 @@ function renderArtworkCard(artwork: Art) { className="Frame1120" title={artwork.name} > - + +
+

+ {artwork.name} +

+

+ from GAME NAME +

+

+ {artwork.description || "No description available."} +

+
+ + {artwork.contributors.length > 0 && ( +
+

+ Contributors +

+
+ {artwork.contributors.map((contributor) => ( + + ))} +
+
+ )} +
+ } + > {!artwork.media && PLACEHOLDER_ICON} @@ -56,12 +143,15 @@ export default function ArtworksPage({ artworks, error }: ArtworksPageProps) { return (
-

+

FEATURED

- {artworks?.results.map(renderArtworkCard)} + {artworks?.results.slice(0, 3).map(renderArtworkCard)}
@@ -81,7 +171,7 @@ export const getServerSideProps: GetServerSideProps< // }; // Fallback to mock data on error - const mockArtworks = generateMockArtworks(12); + const mockArtworks = generateMockArtworks(3); return { props: { artworks: { From 387fbf0c8d3f971395bd86195f91f463991b1403 Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Wed, 21 Jan 2026 11:58:52 +0000 Subject: [PATCH 56/69] fix: styling --- client/src/components/ui/image-card.tsx | 30 +++++++++++++++++-------- client/src/hooks/use-artwork-data.ts | 19 +++++++++++++++- client/src/pages/artwork/index.tsx | 20 +++++++++-------- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/client/src/components/ui/image-card.tsx b/client/src/components/ui/image-card.tsx index 31353bf2..9d22efca 100644 --- a/client/src/components/ui/image-card.tsx +++ b/client/src/components/ui/image-card.tsx @@ -16,17 +16,23 @@ const ImageCard = ({ children, backContent, }: ImageCardProps) => { - const hasBack = Boolean(backContent); - const cardFlipClass = hasBack - ? " group-hover:[transform:rotateY(180deg)]" - : ""; + const [isFlipped, setIsFlipped] = React.useState(false); return ( -
+
backContent && setIsFlipped(true)} + onMouseLeave={() => backContent && setIsFlipped(false)} > -
+
{imageSrc ? ( <> - {hasBack && ( -
+ {backContent && ( +
{backContent}
)} diff --git a/client/src/hooks/use-artwork-data.ts b/client/src/hooks/use-artwork-data.ts index da08c9e9..b8019096 100644 --- a/client/src/hooks/use-artwork-data.ts +++ b/client/src/hooks/use-artwork-data.ts @@ -10,7 +10,24 @@ export const generateMockArtworks = (count: number): Art[] => { //source_game: "Mock Game", media: "", active: true, - contributors: [], + contributors: [ + { + id: i * 10 + 1, + art_id: i, + member_name: "Contributor 1", + role: "artist", + discord_url: "https://discord.com", + instagram_url: "https://instagram.com", + }, + { + id: i * 10 + 2, + art_id: i, + member_name: "Contributor 2", + role: "designer", + discord_url: "https://discord.com", + instagram_url: "https://instagram.com", + }, + ], //created_at: new Date().toISOString(), }); } diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index 9bdfb3aa..9ed7ba99 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -59,17 +59,17 @@ function renderArtworkCard(artwork: Art) { {artwork.contributors.length > 0 && (
-

+

Contributors

-
+
{artwork.contributors.map((contributor) => (
- {contributor.member_name} -
+ {contributor.member_name} +
{contributor.instagram_url && ( Date: Sat, 24 Jan 2026 03:56:35 +0000 Subject: [PATCH 57/69] feat: add interactive artwork cards with flip animation and responsive navigation --- client/src/components/ui/image-card.tsx | 31 +++- client/src/pages/artwork/index.tsx | 192 ++++++++++++------------ 2 files changed, 125 insertions(+), 98 deletions(-) diff --git a/client/src/components/ui/image-card.tsx b/client/src/components/ui/image-card.tsx index 9d22efca..32fba7e0 100644 --- a/client/src/components/ui/image-card.tsx +++ b/client/src/components/ui/image-card.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; +import { useRouter } from "next/router"; import React from "react"; interface ImageCardProps { @@ -8,6 +9,8 @@ interface ImageCardProps { children?: React.ReactNode; /** Optional content rendered on the back when hovering/focused. */ backContent?: React.ReactNode; + /** Optional href for navigation when clicking the front face */ + href?: string; } const ImageCard = ({ @@ -15,19 +18,41 @@ const ImageCard = ({ imageAlt = "Image", children, backContent, + href, }: ImageCardProps) => { + const router = useRouter(); const [isFlipped, setIsFlipped] = React.useState(false); + const [isMobile, setIsMobile] = React.useState(false); + + React.useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth < 768); + }; + + checkMobile(); + window.addEventListener("resize", checkMobile); + return () => window.removeEventListener("resize", checkMobile); + }, []); + + const handleClick = () => { + // On mobile, navigate directly if href is provided + if (isMobile && href) { + router.push(href); + } else if (backContent) { + // On desktop, toggle flip state + setIsFlipped(!isFlipped); + } + }; return (
backContent && setIsFlipped(true)} - onMouseLeave={() => backContent && setIsFlipped(false)} + onClick={handleClick} >
- -
-

- {artwork.name} -

-

- from GAME NAME -

-

- {artwork.description || "No description available."} -

-
+ imageSrc={artwork.media || undefined} + imageAlt={artwork.name} + href={`/artwork/${artwork.id}`} + backContent={ +
+
+

+ {artwork.name} +

+

+ from GAME NAME +

+

+ {artwork.description || "No description available."} +

+
- {artwork.contributors.length > 0 && ( -
-

- Contributors -

-
+ )} + + e.stopPropagation()} + > + VIEW FULL DETAILS + +
+ } + > + {!artwork.media && PLACEHOLDER_ICON} + ); } @@ -152,7 +154,7 @@ export default function ArtworksPage({ artworks, error }: ArtworksPageProps) { FEATURED -
+
{artworks?.results.slice(0, 3).map(renderArtworkCard)}
From ab4a7eeb2d94cec81c3f392b6d387999485d0caa Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Sat, 24 Jan 2026 04:45:52 +0000 Subject: [PATCH 58/69] refactor: remove discord + insta url for frontend --- client/src/hooks/use-artwork-data.ts | 8 ---- client/src/pages/artwork/[id].tsx | 25 +------------ client/src/pages/artwork/index.tsx | 55 +--------------------------- 3 files changed, 2 insertions(+), 86 deletions(-) diff --git a/client/src/hooks/use-artwork-data.ts b/client/src/hooks/use-artwork-data.ts index b8019096..60a65adf 100644 --- a/client/src/hooks/use-artwork-data.ts +++ b/client/src/hooks/use-artwork-data.ts @@ -16,16 +16,12 @@ export const generateMockArtworks = (count: number): Art[] => { art_id: i, member_name: "Contributor 1", role: "artist", - discord_url: "https://discord.com", - instagram_url: "https://instagram.com", }, { id: i * 10 + 2, art_id: i, member_name: "Contributor 2", role: "designer", - discord_url: "https://discord.com", - instagram_url: "https://instagram.com", }, ], //created_at: new Date().toISOString(), @@ -50,16 +46,12 @@ export const generateMockArtwork = (id: string): Art => { art_id: Number(id), member_name: "Contributor 1", role: "user1", - discord_url: "https://discord.com", - instagram_url: "", }, { id: 2, art_id: Number(id), member_name: "Contributor 2", role: "user2", - discord_url: "", - instagram_url: "https://instagram.com", }, ], }; diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 3b2261eb..764210d9 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -1,4 +1,3 @@ -import { Instagram, MessageSquare } from "lucide-react"; import { GetServerSideProps } from "next"; import Image from "next/image"; import { useRouter } from "next/navigation"; @@ -37,32 +36,10 @@ function displayContributors(artwork: Art) { className="ContributorsList relative flex flex-col gap-3 p-3" > {artwork.contributors?.map((contributor) => ( -
+
{contributor.member_name}
-
- {contributor.discord_url && ( - - - - )} - {contributor.instagram_url && ( - - - - )} -
))}
diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index 9ed17cba..78fc9885 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -61,62 +61,9 @@ function renderArtworkCard(artwork: Art) { {artwork.contributors.map((contributor) => ( ))}
From 0a6c08c5a83dc7b178dd1cb951f4ed87d777edb6 Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Sat, 24 Jan 2026 05:05:33 +0000 Subject: [PATCH 59/69] refactor: remove basedto + page-reponse --- client/src/pages/artwork/index.tsx | 8 +++++++- client/src/types/art-contributor.ts | 5 ++--- client/src/types/art.ts | 4 ++-- client/src/types/base-dto.ts | 3 --- client/src/types/page-response.ts | 6 ------ 5 files changed, 11 insertions(+), 15 deletions(-) delete mode 100644 client/src/types/base-dto.ts delete mode 100644 client/src/types/page-response.ts diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index 78fc9885..4e717e27 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -7,7 +7,13 @@ import ErrorModal from "@/components/ui/modal/error-modal"; import { generateMockArtworks } from "@/hooks/use-artwork-data"; import api from "@/lib/api"; import { Art } from "@/types/art"; -import { PageResult } from "@/types/page-response"; + +export interface PageResult { + count: number; + next: string; + previous: string; + results: T[]; +} interface ArtworksPageProps { artworks?: PageResult; diff --git a/client/src/types/art-contributor.ts b/client/src/types/art-contributor.ts index ed38150d..600a941e 100644 --- a/client/src/types/art-contributor.ts +++ b/client/src/types/art-contributor.ts @@ -1,6 +1,5 @@ -import { BaseDto } from "./base-dto"; - -export interface ArtContributor extends BaseDto { +export interface ArtContributor { + id: number; art_id: number; member_name: string; role: string; diff --git a/client/src/types/art.ts b/client/src/types/art.ts index f00c2979..ebb22e13 100644 --- a/client/src/types/art.ts +++ b/client/src/types/art.ts @@ -1,7 +1,7 @@ import { ArtContributor } from "./art-contributor"; -import { BaseDto } from "./base-dto"; -export interface Art extends BaseDto { +export interface Art { + id: number; name: string; description: string; media: string; diff --git a/client/src/types/base-dto.ts b/client/src/types/base-dto.ts deleted file mode 100644 index 9e3b6872..00000000 --- a/client/src/types/base-dto.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface BaseDto { - id: number; -} diff --git a/client/src/types/page-response.ts b/client/src/types/page-response.ts deleted file mode 100644 index e5fa692e..00000000 --- a/client/src/types/page-response.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface PageResult { - count: number; - next: string; - previous: string; - results: T[]; -} From 08b4de62071eec860ea48d8e28f0b6dd0faab990 Mon Sep 17 00:00:00 2001 From: Karl_Sue <24595816@student.uwa.edu.au> Date: Sat, 24 Jan 2026 05:25:43 +0000 Subject: [PATCH 60/69] styling: using font-sans instead of font-jersey10 --- client/src/pages/artwork/[id].tsx | 6 +++--- client/src/pages/artwork/index.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 764210d9..70408b76 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -26,7 +26,7 @@ function displayContributors(artwork: Art) { >
Contributors
@@ -96,7 +96,7 @@ export default function ArtworkPage({ artwork, error }: ArtworkPageProps) {
{artwork!.name}
@@ -120,7 +120,7 @@ export default function ArtworkPage({ artwork, error }: ArtworkPageProps) {
{artwork!.name}
diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index 4e717e27..ea30552b 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -47,7 +47,7 @@ function renderArtworkCard(artwork: Art) { backContent={
-

+

{artwork.name}

@@ -60,7 +60,7 @@ function renderArtworkCard(artwork: Art) { {artwork.contributors.length > 0 && (

-

+

Contributors

@@ -78,7 +78,7 @@ function renderArtworkCard(artwork: Art) { e.stopPropagation()} > VIEW FULL DETAILS @@ -102,7 +102,7 @@ export default function ArtworksPage({ artworks, error }: ArtworksPageProps) {

FEATURED

From ca9ec3921e09cfed8bf196ccd3b9d3df74dfb308 Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Tue, 27 Jan 2026 06:20:33 +0000 Subject: [PATCH 61/69] Fix: Remove deprecated social media fields and unify font usage --- client/src/pages/artwork/[id].tsx | 8 +++---- client/src/pages/artwork/index.tsx | 6 ++--- client/src/types/art-contributor.ts | 2 -- client/tailwind.config.ts | 1 - server/poetry.lock | 36 +++++++++++++++++++++++++---- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/client/src/pages/artwork/[id].tsx b/client/src/pages/artwork/[id].tsx index 70408b76..064e8e18 100644 --- a/client/src/pages/artwork/[id].tsx +++ b/client/src/pages/artwork/[id].tsx @@ -37,7 +37,7 @@ function displayContributors(artwork: Art) { > {artwork.contributors?.map((contributor) => (
-
+
{contributor.member_name}
@@ -64,7 +64,7 @@ export default function ArtworkPage({ artwork, error }: ArtworkPageProps) { >
@@ -108,7 +108,7 @@ export default function ArtworkPage({ artwork, error }: ArtworkPageProps) { data-layer="Artwork Description" className="justify-start self-stretch" > - + {artwork!.description}
@@ -129,7 +129,7 @@ export default function ArtworkPage({ artwork, error }: ArtworkPageProps) { className="DescriptionSectionMobile flex-col items-start justify-start pt-7" >
- + {artwork!.description}
diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index ea30552b..d8d0b38f 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -50,10 +50,10 @@ function renderArtworkCard(artwork: Art) {

{artwork.name}

-

+

from GAME NAME

-

+

{artwork.description || "No description available."}

@@ -67,7 +67,7 @@ function renderArtworkCard(artwork: Art) { {artwork.contributors.map((contributor) => (
{contributor.member_name}
diff --git a/client/src/types/art-contributor.ts b/client/src/types/art-contributor.ts index 600a941e..8afd03b5 100644 --- a/client/src/types/art-contributor.ts +++ b/client/src/types/art-contributor.ts @@ -3,6 +3,4 @@ export interface ArtContributor { art_id: number; member_name: string; role: string; - instagram_url?: string; // TODO [HanMinh] to refine where to get these info - discord_url?: string; } diff --git a/client/tailwind.config.ts b/client/tailwind.config.ts index 9c4bd797..7698c77a 100644 --- a/client/tailwind.config.ts +++ b/client/tailwind.config.ts @@ -22,7 +22,6 @@ const config = { fontFamily: { sans: ["var(--font-sans)", ...fontFamily.sans], jersey10: ["Jersey 10", ...fontFamily.sans], - dmSans: ["DM Sans", ...fontFamily.sans], firaCode: ["var(--font-firaCode)", ...fontFamily.sans], }, diff --git a/server/poetry.lock b/server/poetry.lock index 8e6deac4..3c9c8704 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "asgiref" @@ -6,6 +6,7 @@ version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, @@ -20,6 +21,7 @@ version = "2.15.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" +groups = ["dev"] files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, @@ -35,6 +37,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -46,6 +50,7 @@ version = "5.1.15" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "django-5.1.15-py3-none-any.whl", hash = "sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432"}, {file = "django-5.1.15.tar.gz", hash = "sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947"}, @@ -66,6 +71,7 @@ version = "4.4.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django_cors_headers-4.4.0-py3-none-any.whl", hash = "sha256:5c6e3b7fe870876a1efdfeb4f433782c3524078fa0dc9e0195f6706ce7a242f6"}, {file = "django_cors_headers-4.4.0.tar.gz", hash = "sha256:92cf4633e22af67a230a1456cb1b7a02bb213d6536d2dcb2a4a24092ea9cebc2"}, @@ -81,6 +87,7 @@ version = "3.2.3" description = "Extensions for Django" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a"}, {file = "django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401"}, @@ -95,6 +102,7 @@ version = "3.15.2" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"}, {file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"}, @@ -109,6 +117,7 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -125,6 +134,7 @@ version = "1.4" description = "Plugin to catch bad style specific to Django Projects." optional = false python-versions = ">=3.7.2,<4.0.0" +groups = ["dev"] files = [ {file = "flake8_django-1.4.tar.gz", hash = "sha256:4debba883084191568e3187416d1d6bdd4abd826da988f197a3c36572e9f30de"}, ] @@ -139,6 +149,7 @@ version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -153,6 +164,7 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -174,6 +186,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -185,6 +198,7 @@ version = "1.10.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, @@ -231,6 +245,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -242,6 +257,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -253,6 +269,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -368,7 +385,7 @@ fpx = ["olefile"] mic = ["olefile"] test-arrow = ["pyarrow"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -377,6 +394,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -392,6 +410,7 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -403,6 +422,7 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -414,6 +434,7 @@ version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, @@ -434,6 +455,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -448,6 +470,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -462,6 +485,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -473,6 +497,7 @@ version = "0.5.1" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, @@ -488,6 +513,8 @@ version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, @@ -499,6 +526,7 @@ version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, @@ -573,6 +601,6 @@ files = [ ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" -content-hash = "f804c2f3998772b91e34ad214e5fcafe900bec97675f73046d3bcc79aba0f7db" +content-hash = "f804c2f3998772b91e34ad214e5fcafe900bec97675f73046d3bcc79aba0f7db" \ No newline at end of file From 1f5695780dccfb0d00364a9386df1e06e8cd6e0f Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Fri, 30 Jan 2026 14:19:16 +0000 Subject: [PATCH 62/69] Merge conflicting migrations --- .../game_dev/migrations/0008_merge_20260130_2216.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 server/game_dev/migrations/0008_merge_20260130_2216.py diff --git a/server/game_dev/migrations/0008_merge_20260130_2216.py b/server/game_dev/migrations/0008_merge_20260130_2216.py new file mode 100644 index 00000000..fda3f25b --- /dev/null +++ b/server/game_dev/migrations/0008_merge_20260130_2216.py @@ -0,0 +1,13 @@ +# Generated by Django 6.0 on 2026-01-30 14:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0005_alter_member_profile_picture"), + ("game_dev", "0007_alter_artcontributor_unique_together_and_more"), + ] + + operations = [] From fd39a1124f53479777d29c19a3e77ebdaa9c84ec Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Fri, 30 Jan 2026 14:24:35 +0000 Subject: [PATCH 63/69] Fix flake8 formatting error --- server/game_dev/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/game_dev/views.py b/server/game_dev/views.py index d696728b..465c8a1e 100644 --- a/server/game_dev/views.py +++ b/server/game_dev/views.py @@ -63,6 +63,7 @@ class ArtDetailAPIView(generics.RetrieveAPIView): def get_queryset(self): return Art.objects.all() + class MemberAPIView(generics.RetrieveAPIView): serializer_class = MemberSerializer lookup_field = "id" From bd8ad2b38295959867e1fe031a0bc795c7afaa98 Mon Sep 17 00:00:00 2001 From: Tuan Khanh Hoang Date: Sat, 31 Jan 2026 03:45:54 +0000 Subject: [PATCH 64/69] add: migration --- .../game_dev/migrations/0010_merge_20260131_1145.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 server/game_dev/migrations/0010_merge_20260131_1145.py diff --git a/server/game_dev/migrations/0010_merge_20260131_1145.py b/server/game_dev/migrations/0010_merge_20260131_1145.py new file mode 100644 index 00000000..b998ef79 --- /dev/null +++ b/server/game_dev/migrations/0010_merge_20260131_1145.py @@ -0,0 +1,13 @@ +# Generated by Django 5.1.15 on 2026-01-31 03:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0008_merge_20260130_2216"), + ("game_dev", "0009_merge_20260131_1044"), + ] + + operations = [] From a7e78ab1d7c75d3a2d3db9bd51183f9cd37fa1a4 Mon Sep 17 00:00:00 2001 From: Tuan Khanh Hoang Date: Sat, 7 Feb 2026 04:07:36 +0000 Subject: [PATCH 65/69] refactor: use svg instead of inline declaration --- client/public/go-back-icon.svg | 10 +++++++++ client/public/placeholder-icon.svg | 6 +++++ client/src/components/ui/go-back-button.tsx | 22 ++++++------------- .../src/components/ui/image-placeholder.tsx | 19 ++++++---------- client/src/pages/artwork/index.tsx | 19 ++++++---------- 5 files changed, 37 insertions(+), 39 deletions(-) create mode 100644 client/public/go-back-icon.svg create mode 100644 client/public/placeholder-icon.svg diff --git a/client/public/go-back-icon.svg b/client/public/go-back-icon.svg new file mode 100644 index 00000000..e920f5a5 --- /dev/null +++ b/client/public/go-back-icon.svg @@ -0,0 +1,10 @@ + + + + diff --git a/client/public/placeholder-icon.svg b/client/public/placeholder-icon.svg new file mode 100644 index 00000000..6879e787 --- /dev/null +++ b/client/public/placeholder-icon.svg @@ -0,0 +1,6 @@ + + + diff --git a/client/src/components/ui/go-back-button.tsx b/client/src/components/ui/go-back-button.tsx index 5f1ebce1..53ca3501 100644 --- a/client/src/components/ui/go-back-button.tsx +++ b/client/src/components/ui/go-back-button.tsx @@ -1,3 +1,4 @@ +import Image from "next/image"; import Link from "next/link"; interface GoBackButtonProps { @@ -12,22 +13,13 @@ const GoBackButton = ({ url, label }: GoBackButtonProps) => { type="button" >
- + />

{label}

diff --git a/client/src/components/ui/image-placeholder.tsx b/client/src/components/ui/image-placeholder.tsx index b7e25e58..c694f378 100644 --- a/client/src/components/ui/image-placeholder.tsx +++ b/client/src/components/ui/image-placeholder.tsx @@ -1,3 +1,4 @@ +import Image from "next/image"; import React from "react"; const ImagePlaceholder = () => { @@ -7,18 +8,12 @@ const ImagePlaceholder = () => { className="PlaceholderImage bg-light-2 flex h-[500px] w-[500px] items-center justify-center rounded-[10px]" >
- - - + Placeholder icon
); diff --git a/client/src/pages/artwork/index.tsx b/client/src/pages/artwork/index.tsx index d8d0b38f..3cb76285 100644 --- a/client/src/pages/artwork/index.tsx +++ b/client/src/pages/artwork/index.tsx @@ -1,4 +1,5 @@ import { GetServerSideProps } from "next"; +import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; @@ -22,18 +23,12 @@ interface ArtworksPageProps { const PLACEHOLDER_ICON = (
- - - + Placeholder icon
); From 45c829e91719f0a5046c541fe08f7def52268cb2 Mon Sep 17 00:00:00 2001 From: Peitong Du <101039613+DDuu123321@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:50:43 +0800 Subject: [PATCH 66/69] Update jersey10 font variable in Tailwind config --- client/tailwind.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/tailwind.config.ts b/client/tailwind.config.ts index 7698c77a..156c4005 100644 --- a/client/tailwind.config.ts +++ b/client/tailwind.config.ts @@ -21,7 +21,7 @@ const config = { extend: { fontFamily: { sans: ["var(--font-sans)", ...fontFamily.sans], - jersey10: ["Jersey 10", ...fontFamily.sans], + jersey10: ["var(--font-jersey10)", ...fontFamily.sans], firaCode: ["var(--font-firaCode)", ...fontFamily.sans], }, From 3b82d09ca49e21e0eb3308e457d2ec7d61212f73 Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Sat, 14 Feb 2026 04:13:57 +0000 Subject: [PATCH 67/69] Merge conflicting migrations 0010 --- .../game_dev/migrations/0011_merge_20260214_1212.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 server/game_dev/migrations/0011_merge_20260214_1212.py diff --git a/server/game_dev/migrations/0011_merge_20260214_1212.py b/server/game_dev/migrations/0011_merge_20260214_1212.py new file mode 100644 index 00000000..8568c504 --- /dev/null +++ b/server/game_dev/migrations/0011_merge_20260214_1212.py @@ -0,0 +1,13 @@ +# Generated by Django 6.0 on 2026-02-14 04:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0010_merge_20260131_1118"), + ("game_dev", "0010_merge_20260131_1145"), + ] + + operations = [] From 9b0cb7f559af6ea028b488c56f000c729b5941f9 Mon Sep 17 00:00:00 2001 From: David Du <24074639@student.uwa.edu.au> Date: Sat, 14 Feb 2026 04:20:10 +0000 Subject: [PATCH 68/69] Resolve poetry.lock merge conflict --- server/poetry.lock | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/server/poetry.lock b/server/poetry.lock index 3c9c8704..8e6deac4 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "asgiref" @@ -6,7 +6,6 @@ version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, @@ -21,7 +20,6 @@ version = "2.15.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" -groups = ["dev"] files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, @@ -37,8 +35,6 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -50,7 +46,6 @@ version = "5.1.15" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" -groups = ["main"] files = [ {file = "django-5.1.15-py3-none-any.whl", hash = "sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432"}, {file = "django-5.1.15.tar.gz", hash = "sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947"}, @@ -71,7 +66,6 @@ version = "4.4.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "django_cors_headers-4.4.0-py3-none-any.whl", hash = "sha256:5c6e3b7fe870876a1efdfeb4f433782c3524078fa0dc9e0195f6706ce7a242f6"}, {file = "django_cors_headers-4.4.0.tar.gz", hash = "sha256:92cf4633e22af67a230a1456cb1b7a02bb213d6536d2dcb2a4a24092ea9cebc2"}, @@ -87,7 +81,6 @@ version = "3.2.3" description = "Extensions for Django" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a"}, {file = "django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401"}, @@ -102,7 +95,6 @@ version = "3.15.2" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"}, {file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"}, @@ -117,7 +109,6 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" -groups = ["dev"] files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -134,7 +125,6 @@ version = "1.4" description = "Plugin to catch bad style specific to Django Projects." optional = false python-versions = ">=3.7.2,<4.0.0" -groups = ["dev"] files = [ {file = "flake8_django-1.4.tar.gz", hash = "sha256:4debba883084191568e3187416d1d6bdd4abd826da988f197a3c36572e9f30de"}, ] @@ -149,7 +139,6 @@ version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -164,7 +153,6 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -186,7 +174,6 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -198,7 +185,6 @@ version = "1.10.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, @@ -245,7 +231,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -257,7 +242,6 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -269,7 +253,6 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -385,7 +368,7 @@ fpx = ["olefile"] mic = ["olefile"] test-arrow = ["pyarrow"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions ; python_version < \"3.10\""] +typing = ["typing-extensions"] xmp = ["defusedxml"] [[package]] @@ -394,7 +377,6 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -410,7 +392,6 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -422,7 +403,6 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -434,7 +414,6 @@ version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, @@ -455,7 +434,6 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -470,7 +448,6 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -485,7 +462,6 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -497,7 +473,6 @@ version = "0.5.1" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, @@ -513,8 +488,6 @@ version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" -groups = ["main"] -markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, @@ -526,7 +499,6 @@ version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, @@ -601,6 +573,6 @@ files = [ ] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = "^3.12" -content-hash = "f804c2f3998772b91e34ad214e5fcafe900bec97675f73046d3bcc79aba0f7db" \ No newline at end of file +content-hash = "f804c2f3998772b91e34ad214e5fcafe900bec97675f73046d3bcc79aba0f7db" From 9719dbb27e9d336d33c3d52231416fd1e4c4a575 Mon Sep 17 00:00:00 2001 From: Peitong Du <101039613+DDuu123321@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:43:08 +0800 Subject: [PATCH 69/69] Update poetry.lock --- server/poetry.lock | 250 ++++++++++++++++++++++++--------------------- 1 file changed, 131 insertions(+), 119 deletions(-) diff --git a/server/poetry.lock b/server/poetry.lock index 8e6deac4..34f6c925 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "asgiref" @@ -6,6 +6,7 @@ version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, @@ -20,6 +21,7 @@ version = "2.15.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" +groups = ["dev"] files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, @@ -35,6 +37,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -46,6 +50,7 @@ version = "5.1.15" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "django-5.1.15-py3-none-any.whl", hash = "sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432"}, {file = "django-5.1.15.tar.gz", hash = "sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947"}, @@ -66,6 +71,7 @@ version = "4.4.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django_cors_headers-4.4.0-py3-none-any.whl", hash = "sha256:5c6e3b7fe870876a1efdfeb4f433782c3524078fa0dc9e0195f6706ce7a242f6"}, {file = "django_cors_headers-4.4.0.tar.gz", hash = "sha256:92cf4633e22af67a230a1456cb1b7a02bb213d6536d2dcb2a4a24092ea9cebc2"}, @@ -81,6 +87,7 @@ version = "3.2.3" description = "Extensions for Django" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a"}, {file = "django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401"}, @@ -95,6 +102,7 @@ version = "3.15.2" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"}, {file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"}, @@ -109,6 +117,7 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -125,6 +134,7 @@ version = "1.4" description = "Plugin to catch bad style specific to Django Projects." optional = false python-versions = ">=3.7.2,<4.0.0" +groups = ["dev"] files = [ {file = "flake8_django-1.4.tar.gz", hash = "sha256:4debba883084191568e3187416d1d6bdd4abd826da988f197a3c36572e9f30de"}, ] @@ -139,6 +149,7 @@ version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -153,6 +164,7 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -174,6 +186,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -185,6 +198,7 @@ version = "1.10.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, @@ -231,6 +245,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -242,6 +257,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -249,126 +265,111 @@ files = [ [[package]] name = "pillow" -version = "11.3.0" -description = "Python Imaging Library (Fork)" +version = "12.1.1" +description = "Python Imaging Library (fork)" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, - {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, - {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, - {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, - {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, - {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, - {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, - {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, - {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, - {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, - {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, - {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, - {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, - {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, - {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, - {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, - {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, - {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, - {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, - {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, - {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, - {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, - {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, - {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, - {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, - {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, - {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, + {file = "pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0"}, + {file = "pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713"}, + {file = "pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b"}, + {file = "pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b"}, + {file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4"}, + {file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4"}, + {file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e"}, + {file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff"}, + {file = "pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40"}, + {file = "pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23"}, + {file = "pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9"}, + {file = "pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32"}, + {file = "pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38"}, + {file = "pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5"}, + {file = "pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090"}, + {file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af"}, + {file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b"}, + {file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5"}, + {file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d"}, + {file = "pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c"}, + {file = "pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563"}, + {file = "pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80"}, + {file = "pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052"}, + {file = "pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984"}, + {file = "pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79"}, + {file = "pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293"}, + {file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397"}, + {file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0"}, + {file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3"}, + {file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35"}, + {file = "pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a"}, + {file = "pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6"}, + {file = "pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523"}, + {file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e"}, + {file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9"}, + {file = "pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6"}, + {file = "pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60"}, + {file = "pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2"}, + {file = "pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850"}, + {file = "pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289"}, + {file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e"}, + {file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717"}, + {file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a"}, + {file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029"}, + {file = "pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b"}, + {file = "pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1"}, + {file = "pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a"}, + {file = "pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da"}, + {file = "pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc"}, + {file = "pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c"}, + {file = "pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8"}, + {file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20"}, + {file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13"}, + {file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf"}, + {file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524"}, + {file = "pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986"}, + {file = "pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c"}, + {file = "pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3"}, + {file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af"}, + {file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f"}, + {file = "pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642"}, + {file = "pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd"}, + {file = "pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202"}, + {file = "pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f"}, + {file = "pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f"}, + {file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f"}, + {file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e"}, + {file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0"}, + {file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb"}, + {file = "pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f"}, + {file = "pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15"}, + {file = "pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f"}, + {file = "pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8"}, + {file = "pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9"}, + {file = "pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60"}, + {file = "pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7"}, + {file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f"}, + {file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586"}, + {file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce"}, + {file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8"}, + {file = "pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36"}, + {file = "pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b"}, + {file = "pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e"}, + {file = "pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -test-arrow = ["pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions"] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] xmp = ["defusedxml"] [[package]] @@ -377,6 +378,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -392,6 +394,7 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -403,6 +406,7 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -414,6 +418,7 @@ version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, @@ -434,6 +439,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -448,6 +454,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -462,6 +469,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -469,17 +477,18 @@ files = [ [[package]] name = "sqlparse" -version = "0.5.1" +version = "0.5.4" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, - {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, + {file = "sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb"}, + {file = "sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e"}, ] [package.extras] -dev = ["build", "hatch"] +dev = ["build"] doc = ["sphinx"] [[package]] @@ -488,6 +497,8 @@ version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, @@ -499,6 +510,7 @@ version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, @@ -573,6 +585,6 @@ files = [ ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" -content-hash = "f804c2f3998772b91e34ad214e5fcafe900bec97675f73046d3bcc79aba0f7db" +content-hash = "9576347c536499de99b323235e5722ecff72a250598b689f042441da6d57411c"