From 08ad4c346d66c56690edc7fae14f8834d0ce1c8c Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Wed, 4 Feb 2026 15:29:07 +0800 Subject: [PATCH 01/14] Added Jam model with automatic game population - Added Jam Model with three attributes: a big positive integer id, a name, and list of integer Game object id's called games Explanation of my approach to the problem: The easy way to I think to automatically fill games is to simply override the Jam model's save() function, so the populating script will automatically run every time a Jam object is saved. This function will send a http GET request to a specific URL, using the object's id as a parameter, which will hopefully return json data that lists all the games submitted to the specific jam. A Game object will be created for each listed game (they contain enough information for a detailed object), and then each of those new object's id's in the database will be appended to the Jam object's 'games' list. This may be a bit naive so we will definitely discuss - Registered the Jam model in admin.py with appropriate attributes - Added a new dependency: the python Requests library (https://requests.readthedocs.io/en/latest/), along with it's own dependencies. This is simply needed to make the http GET request in the save() function. We can look at alternatives as well, I think this is just the common python one. --- server/game_dev/admin.py | 8 +- server/game_dev/migrations/0011_jam.py | 21 +++ server/game_dev/models.py | 29 ++++ server/poetry.lock | 192 ++++++++++++++++++++++++- server/pyproject.toml | 1 + 5 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 server/game_dev/migrations/0011_jam.py diff --git a/server/game_dev/admin.py b/server/game_dev/admin.py index d07b4da7..246cd69b 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, Game, Event, GameContributor, GameShowcase, Committee +from .models import Member, Game, Event, GameContributor, GameShowcase, Committee, Jam class MemberAdmin(admin.ModelAdmin): @@ -29,9 +29,15 @@ class CommitteeAdmin(admin.ModelAdmin): raw_id_fields = ["id"] +class JamAdmin(admin.ModelAdmin): + list_display = ("id", "name") + search_fields = ["name"] + + admin.site.register(Member, MemberAdmin) admin.site.register(Event, EventAdmin) admin.site.register(Game, GamesAdmin) admin.site.register(GameContributor, GameContributorAdmin) admin.site.register(GameShowcase, GameShowcaseAdmin) admin.site.register(Committee, CommitteeAdmin) +admin.site.register(Jam, JamAdmin) diff --git a/server/game_dev/migrations/0011_jam.py b/server/game_dev/migrations/0011_jam.py new file mode 100644 index 00000000..8f4688fb --- /dev/null +++ b/server/game_dev/migrations/0011_jam.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.15 on 2026-02-02 10:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('game_dev', '0010_merge_20260131_1118'), + ] + + operations = [ + migrations.CreateModel( + name='Jam', + fields=[ + ('id', models.PositiveBigIntegerField(primary_key=True, serialize=False, unique=True)), + ('name', models.CharField(max_length=75, unique=True, blank=False)), + ('games', models.JSONField(default=list, blank=True)), + ], + ), + ] diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 7947ce66..0d402606 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -1,4 +1,6 @@ from django.db import models +from django.db.utils import IntegrityError +from requests import get class Member(models.Model): @@ -95,3 +97,30 @@ def get_member(self): def __str__(self): return self.id.name + + +class Jam(models.Model): + id = models.PositiveBigIntegerField(primary_key=True, unique=True) + name = models.CharField(max_length=75, unique=True, blank=False) + games = models.JSONField(default=list, blank=True) + + def __str__(self): + return self.name + + def save(self, force_insert=False, force_update=False): + r = get(f"https://itch.io/jam/{self.id}/results.json") + try: + results = r.json()["results"] + except KeyError: + print("Error: No results for this Jam ID could be found") + return + games = [] + for i in results: + try: + cur = Game.objects.get(hostURL=i["url"], name=i["title"]) + games.append(cur.pk) + except IntegrityError: + Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail=i["cover_url"]) + games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) + self.games = games + return super().save(force_insert, force_update) diff --git a/server/poetry.lock b/server/poetry.lock index 9e7859f1..7a53c34c 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -31,6 +31,141 @@ files = [ lazy-object-proxy = ">=1.4.0" wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} +[[package]] +name = "certifi" +version = "2026.1.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, + {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -180,6 +315,21 @@ setproctitle = ["setproctitle"] testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -479,6 +629,28 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "six" version = "1.16.0" @@ -520,6 +692,24 @@ files = [ {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] +[[package]] +name = "urllib3" +version = "2.6.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, +] + +[package.extras] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] + [[package]] name = "wrapt" version = "1.16.0" @@ -603,4 +793,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "f804c2f3998772b91e34ad214e5fcafe900bec97675f73046d3bcc79aba0f7db" +content-hash = "f4798bb9174a2b4e4c5e8375d4f07b9d1e04a120d84f8607e02b48fc38b25cb4" diff --git a/server/pyproject.toml b/server/pyproject.toml index 5ec547cb..97efd7e0 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -16,6 +16,7 @@ gunicorn = "^23.0.0" python-dotenv = "^1.0.1" django-extensions = "^3.2.3" pillow = "^11.3.0" +requests = "^2.32.5" [tool.poetry.group.dev.dependencies] From f278bd70320de1485ba32d6344927dd85691491c Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 7 Feb 2026 12:41:25 +0800 Subject: [PATCH 02/14] Reworked Game Jam model -Removed Jam model and migration file - Added the id and games attributes from the Jam model as optional attributes to the event model instead. The same save() function in the Jam model has been adjusted and added to the event model, where both fields will be forced to be blank if a valid jam id isn't provided. - Removed Jam model configuration from admin.py --- server/game_dev/admin.py | 8 +---- .../0011_event_games_event_jamid.py | 23 ++++++++++++++ server/game_dev/migrations/0011_jam.py | 21 ------------- server/game_dev/models.py | 31 +++++++++++++++++-- 4 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 server/game_dev/migrations/0011_event_games_event_jamid.py delete mode 100644 server/game_dev/migrations/0011_jam.py diff --git a/server/game_dev/admin.py b/server/game_dev/admin.py index 246cd69b..d07b4da7 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, Game, Event, GameContributor, GameShowcase, Committee, Jam +from .models import Member, Game, Event, GameContributor, GameShowcase, Committee class MemberAdmin(admin.ModelAdmin): @@ -29,15 +29,9 @@ class CommitteeAdmin(admin.ModelAdmin): raw_id_fields = ["id"] -class JamAdmin(admin.ModelAdmin): - list_display = ("id", "name") - search_fields = ["name"] - - admin.site.register(Member, MemberAdmin) admin.site.register(Event, EventAdmin) admin.site.register(Game, GamesAdmin) admin.site.register(GameContributor, GameContributorAdmin) admin.site.register(GameShowcase, GameShowcaseAdmin) admin.site.register(Committee, CommitteeAdmin) -admin.site.register(Jam, JamAdmin) diff --git a/server/game_dev/migrations/0011_event_games_event_jamid.py b/server/game_dev/migrations/0011_event_games_event_jamid.py new file mode 100644 index 00000000..8b7544e3 --- /dev/null +++ b/server/game_dev/migrations/0011_event_games_event_jamid.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.15 on 2026-02-07 04:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('game_dev', '0011_jam'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='games', + field=models.JSONField(blank=True, default=list, help_text='Only filled if event is a Game Jam'), + ), + migrations.AddField( + model_name='event', + name='jamID', + field=models.PositiveBigIntegerField(blank=True, null=True, unique=True), + ), + ] diff --git a/server/game_dev/migrations/0011_jam.py b/server/game_dev/migrations/0011_jam.py deleted file mode 100644 index 8f4688fb..00000000 --- a/server/game_dev/migrations/0011_jam.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.1.15 on 2026-02-02 10:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0010_merge_20260131_1118'), - ] - - operations = [ - migrations.CreateModel( - name='Jam', - fields=[ - ('id', models.PositiveBigIntegerField(primary_key=True, serialize=False, unique=True)), - ('name', models.CharField(max_length=75, unique=True, blank=False)), - ('games', models.JSONField(default=list, blank=True)), - ], - ), - ] diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 0d402606..62843767 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.db.utils import IntegrityError from requests import get @@ -21,10 +20,35 @@ class Event(models.Model): publicationDate = models.DateField() cover_image = models.ImageField(upload_to="events/", null=True) location = models.CharField(max_length=256) + jamID = models.PositiveBigIntegerField(unique=True, blank=True, null=True) + games = models.JSONField(default=list, blank=True, help_text="Only filled if event is a Game Jam") def __str__(self): return self.name + def save(self, force_insert=False, force_update=False): + if self.jamID is not None: + r = get(f"https://itch.io/jam/{self.jamID}/results.json") + try: + results = r.json()["results"] + except KeyError: + print("Error: No results for this Jam ID could be found") + self.jamID = None + self.games = [] + return super().save(force_insert, force_update) + games = [] + for i in results: + try: + cur = Game.objects.get(hostURL=i["url"], name=i["title"]) + games.append(cur.pk) + except Game.DoesNotExist: + Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail=i["cover_url"]) + games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) + self.games = games + else: + self.games = [] + return super().save(force_insert, force_update) + # GameContributor table: links Game, Member, and role (composite PK) class GameContributor(models.Model): @@ -99,6 +123,7 @@ def __str__(self): return self.id.name +""" class Jam(models.Model): id = models.PositiveBigIntegerField(primary_key=True, unique=True) name = models.CharField(max_length=75, unique=True, blank=False) @@ -119,8 +144,8 @@ def save(self, force_insert=False, force_update=False): try: cur = Game.objects.get(hostURL=i["url"], name=i["title"]) games.append(cur.pk) - except IntegrityError: + except Game.DoesNotExist: Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail=i["cover_url"]) games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) self.games = games - return super().save(force_insert, force_update) + return super().save(force_insert, force_update)""" From 154c65df614e3f0bf881590a08c9b89a53866d22 Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 7 Feb 2026 13:19:11 +0800 Subject: [PATCH 03/14] Fixed migration error - Removed newest migration's dependency on the deleted Jam migration --- server/game_dev/migrations/0011_event_games_event_jamid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/game_dev/migrations/0011_event_games_event_jamid.py b/server/game_dev/migrations/0011_event_games_event_jamid.py index 8b7544e3..48c98292 100644 --- a/server/game_dev/migrations/0011_event_games_event_jamid.py +++ b/server/game_dev/migrations/0011_event_games_event_jamid.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('game_dev', '0011_jam'), + ('game_dev', "0010_merge_20260131_1118"), ] operations = [ From ff1cb736dbf063051058bbc8fbab8e9d02316cdb Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sun, 8 Feb 2026 17:18:46 +0800 Subject: [PATCH 04/14] Refined save() and changed help_text - Added more exception handling for the save() function in the Event model, now handling the http GET request as well - Made a helper function called jamFail() to reduce redundancy - Modified how the thumbnails for Game objects are initialised in the save() function, such that each game's image is directly downloaded into the backend by making a GET request to the "cover_url" attribute - Changed help_text parameter for jamID to be more understandable --- .../0011_event_games_event_jamid.py | 2 +- server/game_dev/models.py | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/server/game_dev/migrations/0011_event_games_event_jamid.py b/server/game_dev/migrations/0011_event_games_event_jamid.py index 48c98292..548b400e 100644 --- a/server/game_dev/migrations/0011_event_games_event_jamid.py +++ b/server/game_dev/migrations/0011_event_games_event_jamid.py @@ -18,6 +18,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='event', name='jamID', - field=models.PositiveBigIntegerField(blank=True, null=True, unique=True), + field=models.PositiveBigIntegerField(blank=True, null=True, unique=True, help_text="See documentation on how to find"), ), ] diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 53a963aa..c652c089 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -1,6 +1,6 @@ from django.db import models from requests import get - +from django.core.files.base import ContentFile class Member(models.Model): name = models.CharField(max_length=200) @@ -20,21 +20,28 @@ class Event(models.Model): publicationDate = models.DateField() cover_image = models.ImageField(upload_to="events/", null=True) location = models.CharField(max_length=256) - jamID = models.PositiveBigIntegerField(unique=True, blank=True, null=True) + jamID = models.PositiveBigIntegerField(unique=True, blank=True, null=True, help_text="See documentation on how to find") games = models.JSONField(default=list, blank=True, help_text="Only filled if event is a Game Jam") def __str__(self): return self.name def save(self, force_insert=False, force_update=False): + def jamFail(): + self.jamID = None + self.games = [] if self.jamID is not None: - r = get(f"https://itch.io/jam/{self.jamID}/results.json") + try: + r = get(f"https://itch.io/jam/{self.jamID}/results.json") + except Exception as e: + print(e) + jamFail() + return super().save(force_insert, force_update) try: results = r.json()["results"] except KeyError: print("Error: No results for this Jam ID could be found") - self.jamID = None - self.games = [] + jamFail() return super().save(force_insert, force_update) games = [] for i in results: @@ -42,7 +49,14 @@ def save(self, force_insert=False, force_update=False): cur = Game.objects.get(hostURL=i["url"], name=i["title"]) games.append(cur.pk) except Game.DoesNotExist: - Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail=i["cover_url"]) + #Uploads each image to the backend from their urls + imageURL = i["cover_url"] + image = get(imageURL) + imageName = imageURL.split("/")[-1] + imageContent = ContentFile(image.content) + + Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail="") + Game.objects.get(name=i["title"], hostURL=i["url"]).thumbnail.save(imageName, imageContent, save=True) games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) self.games = games else: From 161dac22078773b594fc84bf751584cbcb6ec0b0 Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Wed, 11 Feb 2026 18:09:37 +0800 Subject: [PATCH 05/14] Linked individual game objects to their jam - Updated save() function for Event model such that every Game object created for a Jam has it's event attribute set to the corresponding Event object being saved --- server/game_dev/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index c652c089..204ac5c0 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -55,7 +55,7 @@ def jamFail(): imageName = imageURL.split("/")[-1] imageContent = ContentFile(image.content) - Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail="") + Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail="" ,event=self) Game.objects.get(name=i["title"], hostURL=i["url"]).thumbnail.save(imageName, imageContent, save=True) games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) self.games = games From 4e570e1d9ad681ebb13a13b9c4a2854b6b0baba0 Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Fri, 13 Feb 2026 20:55:28 +0800 Subject: [PATCH 06/14] Update poetry.lock --- server/poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/poetry.lock b/server/poetry.lock index d573514e..2b708ec7 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -777,4 +777,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "9576347c536499de99b323235e5722ecff72a250598b689f042441da6d57411c" +content-hash = "e88f3f2557f552326d0466b0abbcbd5e601c6a46307645923e47852b2f17dd49" From e790e29fdf7a254fc9926ed790336f7ee273c865 Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 14 Feb 2026 15:30:53 +0800 Subject: [PATCH 07/14] Added Game Contributor autofilling + .. - Updated id of the my migration to the Event model unique to this branch, so it is most recent and thus no merge migration is required - Added logic in the save() function in the Event model, where it will check if the username of each of the contributors for a game is registered in a current SocialMedia object, where it will then create a GameContributor object using the Member object linked to the SocialMedia object --- ...es_event_jamid.py => 0014_event_games_event_jamid.py} | 2 +- server/game_dev/models.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) rename server/game_dev/migrations/{0011_event_games_event_jamid.py => 0014_event_games_event_jamid.py} (92%) diff --git a/server/game_dev/migrations/0011_event_games_event_jamid.py b/server/game_dev/migrations/0014_event_games_event_jamid.py similarity index 92% rename from server/game_dev/migrations/0011_event_games_event_jamid.py rename to server/game_dev/migrations/0014_event_games_event_jamid.py index 548b400e..4bb25e04 100644 --- a/server/game_dev/migrations/0011_event_games_event_jamid.py +++ b/server/game_dev/migrations/0014_event_games_event_jamid.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('game_dev', "0010_merge_20260131_1118"), + ('game_dev', "0013_merge_20260214_1347"), ] operations = [ diff --git a/server/game_dev/models.py b/server/game_dev/models.py index da0ac921..359b5fb7 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -58,6 +58,15 @@ def jamFail(): Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail="" ,event=self) Game.objects.get(name=i["title"], hostURL=i["url"]).thumbnail.save(imageName, imageContent, save=True) games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) + + contributors = i["contributors"] + for x in contributors: + try: + socialMedia = SocialMedia.objects.get(socialMediaUsername=x["name"]) + except Exception as e: + continue + GameContributor.objects.create(Game=Game.objects.get(name=i["title"], hostURL=i["url"]), Member=socialMedia.member, role="Please add role manually") + self.games = games else: self.games = [] From 4808fae5ffa0abea0eef0f5ab816e4aef71a3a5e Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 14 Feb 2026 15:58:27 +0800 Subject: [PATCH 08/14] Fixed some spelling mistakes - Corrected spelling for some model attributes --- server/game_dev/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 359b5fb7..9887a50e 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -62,10 +62,10 @@ def jamFail(): contributors = i["contributors"] for x in contributors: try: - socialMedia = SocialMedia.objects.get(socialMediaUsername=x["name"]) + socialMedia = SocialMedia.objects.get(socialMediaUserName=x["name"]) except Exception as e: continue - GameContributor.objects.create(Game=Game.objects.get(name=i["title"], hostURL=i["url"]), Member=socialMedia.member, role="Please add role manually") + GameContributor.objects.create(game=Game.objects.get(name=i["title"], hostURL=i["url"]), member=socialMedia.member, role="Please add role manually") self.games = games else: From 309507bd9089c4761d525dfeb38c559b9da7672a Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Fri, 20 Feb 2026 21:41:39 +0800 Subject: [PATCH 09/14] Minor Changes and migration refactor - Made minor changes to models.py to appease flake8 - Renamed the migration that creates the jamID and games fields for the Event model to 0015 as the latest, with the latest merged migration as its dependency. This is to avoid an unnecessary migration merge. --- ...mid.py => 0015_event_games_event_jamid.py} | 2 +- server/game_dev/models.py | 44 +++++-------------- 2 files changed, 11 insertions(+), 35 deletions(-) rename server/game_dev/migrations/{0014_event_games_event_jamid.py => 0015_event_games_event_jamid.py} (92%) diff --git a/server/game_dev/migrations/0014_event_games_event_jamid.py b/server/game_dev/migrations/0015_event_games_event_jamid.py similarity index 92% rename from server/game_dev/migrations/0014_event_games_event_jamid.py rename to server/game_dev/migrations/0015_event_games_event_jamid.py index 4bb25e04..74e4f1b3 100644 --- a/server/game_dev/migrations/0014_event_games_event_jamid.py +++ b/server/game_dev/migrations/0015_event_games_event_jamid.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('game_dev', "0013_merge_20260214_1347"), + ('game_dev', "0014_merge_20260214_1420"), ] operations = [ diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 73e14289..579bd420 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -2,6 +2,7 @@ from requests import get from django.core.files.base import ContentFile + class Member(models.Model): name = models.CharField(max_length=200) active = models.BooleanField(default=True) @@ -30,6 +31,7 @@ def save(self, force_insert=False, force_update=False): def jamFail(): self.jamID = None self.games = [] + if self.jamID is not None: try: r = get(f"https://itch.io/jam/{self.jamID}/results.json") @@ -49,24 +51,26 @@ def jamFail(): cur = Game.objects.get(hostURL=i["url"], name=i["title"]) games.append(cur.pk) except Game.DoesNotExist: - #Uploads each image to the backend from their urls + # Uploads each image to the backend from their url imageURL = i["cover_url"] image = get(imageURL) imageName = imageURL.split("/")[-1] imageContent = ContentFile(image.content) - - Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail="" ,event=self) + + Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail="", event=self) Game.objects.get(name=i["title"], hostURL=i["url"]).thumbnail.save(imageName, imageContent, save=True) games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) - + contributors = i["contributors"] for x in contributors: try: socialMedia = SocialMedia.objects.get(socialMediaUserName=x["name"]) except Exception as e: + print(e) continue - GameContributor.objects.create(game=Game.objects.get(name=i["title"], hostURL=i["url"]), member=socialMedia.member, role="Please add role manually") - + GameContributor.objects.create(game=Game.objects.get(name=i["title"], hostURL=i["url"]), + member=socialMedia.member, role="Please add role manually") + self.games = games else: self.games = [] @@ -167,31 +171,3 @@ def get_member(self): def __str__(self): return self.id.name - - -""" -class Jam(models.Model): - id = models.PositiveBigIntegerField(primary_key=True, unique=True) - name = models.CharField(max_length=75, unique=True, blank=False) - games = models.JSONField(default=list, blank=True) - - def __str__(self): - return self.name - - def save(self, force_insert=False, force_update=False): - r = get(f"https://itch.io/jam/{self.id}/results.json") - try: - results = r.json()["results"] - except KeyError: - print("Error: No results for this Jam ID could be found") - return - games = [] - for i in results: - try: - cur = Game.objects.get(hostURL=i["url"], name=i["title"]) - games.append(cur.pk) - except Game.DoesNotExist: - Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail=i["cover_url"]) - games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) - self.games = games - return super().save(force_insert, force_update)""" From 25835de8a0c26f0e86588c11444925a212fcd569 Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 21 Feb 2026 09:39:27 +0800 Subject: [PATCH 10/14] Added exception handling - Added a failsafe for if a Jam's json does have a results key, but the list of results is empty - Added exception handling for downloading and saving a game's cover image - Appeased flake8 --- server/game_dev/models.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 579bd420..a5fab0ca 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -45,31 +45,39 @@ def jamFail(): print("Error: No results for this Jam ID could be found") jamFail() return super().save(force_insert, force_update) + if len(results) == 0: + print("Error: No results for this Jam ID could be found") + jamFail() + return super().save(force_insert, force_update) games = [] for i in results: try: cur = Game.objects.get(hostURL=i["url"], name=i["title"]) games.append(cur.pk) except Game.DoesNotExist: - # Uploads each image to the backend from their url + Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail="", event=self) + imageURL = i["cover_url"] - image = get(imageURL) - imageName = imageURL.split("/")[-1] - imageContent = ContentFile(image.content) + try: + # Uploads each image to the backend from their url + image = get(imageURL) + imageName = imageURL.split("/")[-1] + imageContent = ContentFile(image.content) + Game.objects.get(name=i["title"], hostURL=i["url"]).thumbnail.save(imageName, imageContent, save=True) + except Exception as e: + print(f"Error: {e}, most likely an invalid url for a game's cover image") - Game.objects.create(name=i["title"], completion=4, hostURL=i["url"], thumbnail="", event=self) - Game.objects.get(name=i["title"], hostURL=i["url"]).thumbnail.save(imageName, imageContent, save=True) games.append(Game.objects.get(name=i["title"], hostURL=i["url"]).pk) - contributors = i["contributors"] for x in contributors: try: socialMedia = SocialMedia.objects.get(socialMediaUserName=x["name"]) + GameContributor.objects.create(game=Game.objects.get(name=i["title"], hostURL=i["url"]), + member=socialMedia.member, role="Please add role manually") except Exception as e: print(e) continue - GameContributor.objects.create(game=Game.objects.get(name=i["title"], hostURL=i["url"]), - member=socialMedia.member, role="Please add role manually") + self.games = games else: From 48a656c55094a6dd63c050ae3b085ed3bc0e3fc0 Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 21 Feb 2026 12:53:23 +0800 Subject: [PATCH 11/14] Made Event object save itself first - Moved the super().save() function to the top of the Event model save() function, so the Event object itself is saved before the games are autofilled. This was to prevent an error where a created Game linked the Event object as a foreign key before it was saved. --- server/game_dev/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index a5fab0ca..091623e7 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -28,6 +28,7 @@ def __str__(self): return self.name def save(self, force_insert=False, force_update=False): + super().save(force_insert, force_update) def jamFail(): self.jamID = None self.games = [] @@ -38,17 +39,17 @@ def jamFail(): except Exception as e: print(e) jamFail() - return super().save(force_insert, force_update) + return try: results = r.json()["results"] except KeyError: print("Error: No results for this Jam ID could be found") jamFail() - return super().save(force_insert, force_update) + return if len(results) == 0: print("Error: No results for this Jam ID could be found") jamFail() - return super().save(force_insert, force_update) + return games = [] for i in results: try: @@ -78,11 +79,10 @@ def jamFail(): print(e) continue - self.games = games else: self.games = [] - return super().save(force_insert, force_update) + return # GameContributor table: links Game, Member, and role (composite PK) From 53771aae18de0719eea3fff6e76b0eb212e4652c Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 21 Feb 2026 13:34:00 +0800 Subject: [PATCH 12/14] Flake8 --- server/game_dev/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 091623e7..ea4ca05a 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -29,6 +29,7 @@ def __str__(self): def save(self, force_insert=False, force_update=False): super().save(force_insert, force_update) + def jamFail(): self.jamID = None self.games = [] @@ -73,12 +74,12 @@ def jamFail(): for x in contributors: try: socialMedia = SocialMedia.objects.get(socialMediaUserName=x["name"]) - GameContributor.objects.create(game=Game.objects.get(name=i["title"], hostURL=i["url"]), - member=socialMedia.member, role="Please add role manually") + GameContributor.objects.create(game=Game.objects.get(name=i["title"], hostURL=i["url"], + member=socialMedia.member, role="Please add role manually")) except Exception as e: print(e) continue - + self.games = games else: self.games = [] From e8954b4e081986cbf615599f4081d6f3c9cc73de Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 21 Feb 2026 13:53:49 +0800 Subject: [PATCH 13/14] Added back super().save() for some parts Added super().save() back to every return statement in the Event model's save() function, so the object will be saved at the start and also saved at the end of the save() function so the games and jamID attributes are fully updated --- server/game_dev/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index ea4ca05a..ffbe8806 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -40,17 +40,17 @@ def jamFail(): except Exception as e: print(e) jamFail() - return + return super().save(force_insert, force_update) try: results = r.json()["results"] except KeyError: print("Error: No results for this Jam ID could be found") jamFail() - return + return super().save(force_insert, force_update) if len(results) == 0: print("Error: No results for this Jam ID could be found") jamFail() - return + return super().save(force_insert, force_update) games = [] for i in results: try: @@ -83,7 +83,7 @@ def jamFail(): self.games = games else: self.games = [] - return + return super().save(force_insert, force_update) # GameContributor table: links Game, Member, and role (composite PK) From e3c7d7de1dba4d0209a0085f35570c9fa6dd6955 Mon Sep 17 00:00:00 2001 From: Games4Doritos Date: Sat, 21 Feb 2026 15:00:27 +0800 Subject: [PATCH 14/14] Made all save() functions asynchronous - Switched all instances of the save() function to asave() (asynchronous save) instead. I think this solves some UNIQUE constraint integrity errors from testing --- server/game_dev/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index ffbe8806..d0ca0859 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -27,8 +27,8 @@ class Event(models.Model): def __str__(self): return self.name - def save(self, force_insert=False, force_update=False): - super().save(force_insert, force_update) + def asave(self, force_insert=False, force_update=False, using="default", update_fields=None): + super().save(force_insert, force_update, using, update_fields) def jamFail(): self.jamID = None @@ -40,17 +40,17 @@ def jamFail(): except Exception as e: print(e) jamFail() - return super().save(force_insert, force_update) + return super().asave(force_insert, force_update, using, update_fields) try: results = r.json()["results"] except KeyError: print("Error: No results for this Jam ID could be found") jamFail() - return super().save(force_insert, force_update) + return super().asave(force_insert, force_update, using, update_fields) if len(results) == 0: print("Error: No results for this Jam ID could be found") jamFail() - return super().save(force_insert, force_update) + return super().asave(force_insert, force_update, using, update_fields) games = [] for i in results: try: @@ -83,7 +83,7 @@ def jamFail(): self.games = games else: self.games = [] - return super().save(force_insert, force_update) + return super().asave(force_insert, force_update, using, update_fields) # GameContributor table: links Game, Member, and role (composite PK)