-
Notifications
You must be signed in to change notification settings - Fork 0
[Interactive Fiction] Database storing twine files in byte type #1291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 27 commits
426c8cd
957393d
ecb9e8e
a081cd6
dd04cd0
fb374be
668be7a
2ce0f25
b7ad807
6a3c94d
4e532c2
925a565
87c1375
8e8399c
2c70f21
a7722c6
c51f955
819e992
931a566
7910929
4722e38
7acb811
a332b75
8ec00ad
c88fe3e
c7d42be
493eecf
7ab64d4
784bd94
8565eef
490eb20
51e50d1
c27fed1
48639a2
494ad2c
3c01eee
7ed8cfb
65ffd8b
3700b42
f856ec5
4a90f26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,3 @@ | ||
| # Generated by Django 4.2.20 on 2025-05-19 22:15 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # Generated by Django 5.1.7 on 2025-05-21 00:36 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("games", "0038_merge_20250519_0011"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="game", | ||
| name="twine_file_content", | ||
| field=models.BinaryField(blank=True, null=True), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="game", | ||
| name="twine_file_name", | ||
| field=models.CharField(blank=True, max_length=255, null=True), | ||
| ), | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Generated by Django 5.1.7 on 2025-05-21 01:07 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("games", "0039_game_twine_file_content_game_twine_file_name"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.RemoveField( | ||
| model_name="game", | ||
| name="twine_file_content", | ||
| ), | ||
| migrations.AlterField( | ||
| model_name="game", | ||
| name="twine_file", | ||
| field=models.BinaryField(blank=True, null=True), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Generated by Django 4.2.20 on 2025-05-21 04:17 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("games", "0040_remove_game_twine_file_content_alter_game_twine_file"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.RenameField( | ||
| model_name="game", | ||
| old_name="twine_file", | ||
| new_name="twine_file_content", | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Generated by Django 4.2.20 on 2025-05-21 04:26 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("games", "0041_rename_twine_file_game_twine_file_content"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.RenameField( | ||
| model_name="game", | ||
| old_name="twine_file_content", | ||
| new_name="twine_file", | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Generated by Django 4.2.20 on 2025-05-21 05:05 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("games", "0042_rename_twine_file_content_game_twine_file"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.RemoveField( | ||
| model_name="game", | ||
| name="twine_file_name", | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Generated by Django 4.2.20 on 2025-05-21 05:06 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
||
|
|
||
| dependencies = [ | ||
| ("games", "0043_remove_game_twine_file_name"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="game", | ||
| name="twine_file_name", | ||
| field=models.CharField(blank=True, max_length=255, null=True), | ||
| ), | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Generated by Django 4.2.20 on 2025-05-21 19:34 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("games", "0040_merge_20250521_1413"), | ||
| ("games", "0044_game_twine_file_name"), | ||
| ] | ||
|
|
||
| operations = [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ | |
| from django.core.paginator import Paginator | ||
| from django.db.models import Avg, Case, Count, ExpressionWrapper, F, FloatField, Q, Value, When | ||
| from django.db.models.functions import Lower | ||
| from django.http import HttpResponseForbidden, HttpResponseRedirect, JsonResponse | ||
| from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect, JsonResponse | ||
| from django.shortcuts import get_object_or_404, redirect, render | ||
| from django.urls import reverse, reverse_lazy | ||
| from django.utils import timezone | ||
|
|
@@ -105,7 +105,7 @@ def get_context_data(self, **kwargs): | |
| context["avg_rating"] = self.object.reviews.filter(is_public=True).aggregate(Avg("rating"))["rating__avg"] | ||
|
|
||
| # FOR IF/twine GAMES | ||
| context["is_twine_game"] = self.object.twine_file.name.endswith(".html") if self.object.twine_file else False | ||
| context["is_twine_game"] = self.object.twine_file is not None | ||
| context["recommended_games"] = get_recommended_games( | ||
| game=self.object, | ||
| user=self.request.user if self.request.user.is_authenticated else None, | ||
|
|
@@ -152,14 +152,18 @@ def get_context_data(self, **kwargs): | |
| # Ensure the uploaded Twine .html file is saved to the Game model | ||
| def form_valid(self, form): | ||
| self.object = form.save(commit=False) | ||
| # ✅ Manually assign uploaded file | ||
| if self.request.FILES.get("twine_file"): | ||
| self.object.twine_file = self.request.FILES["twine_file"] | ||
| # save both the file and its contents | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, readable comments |
||
| uploaded_file = self.request.FILES.get("twine_file") | ||
| if uploaded_file: | ||
| self.object.twine_file_name = uploaded_file.name | ||
| self.object.twine_file = uploaded_file.read() | ||
| uploaded_file.seek(0) | ||
|
|
||
| self.object.save() | ||
| return redirect(self.get_success_url()) | ||
|
|
||
| def get_success_url(self): | ||
| if self.object.twine_file and self.object.twine_file.name.endswith(".html"): | ||
| if self.object.twine_file_name and self.object.twine_file_name.endswith(".html"): | ||
| return reverse("interactive-fiction-detail", kwargs={"pk": self.object.pk}) | ||
| return reverse("game-detail", kwargs={"pk": self.object.pk}) | ||
|
|
||
|
|
@@ -184,6 +188,20 @@ def get_context_data(self, **kwargs): | |
| return context | ||
|
|
||
|
|
||
| # ===========For storing twine in database ============== | ||
|
|
||
|
|
||
| def serve_twine_from_db(request, pk): | ||
| game = get_object_or_404(Game, pk=pk) | ||
| if not game.twine_file: | ||
| raise Http404("No Twine file stored in database.") | ||
| return HttpResponse( | ||
| game.twine_file, | ||
| content_type="text/html", | ||
| headers={"Content-Disposition": f'inline; filename="{game.twine_file_name}"'}, | ||
| ) | ||
|
|
||
|
|
||
| # =============== BGG Searching ================= | ||
| # The following functions involve using the BoardGameGeek API to search for games. | ||
| # API documentation: https://boardgamegeek.com/wiki/page/BGG_XML_API2 | ||
|
|
@@ -564,7 +582,7 @@ def get_context_data(self, **kwargs): | |
| context["game"] = latest_game | ||
|
|
||
| if latest_game.twine_file: | ||
| context["uploaded_file_url"] = latest_game.twine_file.url | ||
| context["has_uploaded_twine"] = bool(latest_game.twine_file) | ||
|
|
||
| return context | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # Generated by Django 5.1.7 on 2025-05-21 01:03 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| initial = True | ||
|
|
||
| dependencies = [ | ||
| ("games", "0039_game_twine_file_content_game_twine_file_name"), | ||
| ("users", "0013_merge_20250519_0011"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="Region", | ||
| fields=[ | ||
| ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), | ||
| ("continent", models.CharField(max_length=100)), | ||
| ("country", models.CharField(max_length=100)), | ||
| ("region", models.CharField(max_length=100)), | ||
| ], | ||
| ), | ||
| migrations.CreateModel( | ||
| name="Leaderboard", | ||
| fields=[ | ||
| ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), | ||
| ("name", models.CharField(max_length=255)), | ||
| ("description", models.TextField(blank=True)), | ||
| ( | ||
| "game", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, related_name="leaderboards", to="games.game" | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| migrations.CreateModel( | ||
| name="LeaderboardEntry", | ||
| fields=[ | ||
| ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), | ||
| ("rank", models.IntegerField()), | ||
| ( | ||
| "leaderboard", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="entries", | ||
| to="leaderboards.leaderboard", | ||
| ), | ||
| ), | ||
| ( | ||
| "user", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="leaderboard_entries", | ||
| to="users.userprofile", | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| migrations.CreateModel( | ||
| name="Metric", | ||
| fields=[ | ||
| ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), | ||
| ("name", models.CharField(max_length=255)), | ||
| ("unit", models.CharField(max_length=50)), | ||
| ("description", models.TextField(blank=True)), | ||
| ( | ||
| "game", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, related_name="metrics", to="games.game" | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| migrations.CreateModel( | ||
| name="MetricScore", | ||
| fields=[ | ||
| ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), | ||
| ("score", models.IntegerField()), | ||
| ( | ||
| "leaderboard_entry", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="metric_scores", | ||
| to="leaderboards.leaderboardentry", | ||
| ), | ||
| ), | ||
| ( | ||
| "match", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, related_name="metric_scores", to="games.match" | ||
| ), | ||
| ), | ||
| ( | ||
| "metric", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="metric_scores", | ||
| to="leaderboards.metric", | ||
| ), | ||
| ), | ||
| ( | ||
| "user", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="metric_scores", | ||
| to="users.userprofile", | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| migrations.CreateModel( | ||
| name="LeaderboardPrivacySetting", | ||
| fields=[ | ||
| ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), | ||
| ("complete_opt_out", models.BooleanField(default=False)), | ||
| ("display_as_anonymous", models.BooleanField(default=False)), | ||
| ( | ||
| "game", | ||
| models.ForeignKey( | ||
| blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="games.game" | ||
| ), | ||
| ), | ||
| ( | ||
| "leaderboard", | ||
| models.ForeignKey( | ||
| blank=True, | ||
| null=True, | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| to="leaderboards.leaderboard", | ||
| ), | ||
| ), | ||
| ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="users.userprofile")), | ||
| ], | ||
| options={ | ||
| "unique_together": {("user", "game", "leaderboard")}, | ||
| }, | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,11 @@ <h1> | |
| </div> | ||
| <br /> | ||
| {% endfor %} | ||
| <!-- Twine upload (manual field) --> | ||
| <label for="id_twine_file">Upload Twine Game (.html):</label> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking this will be updated once we merge other templates in other branches into dev |
||
| <input type="file" name="twine_file" id="id_twine_file" accept=".html" /> | ||
| <br /> | ||
| <br /> | ||
| <button type="submit">Save</button> | ||
| </form> | ||
| <!-- AJAX script for BGG search (only in Create view) --> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smart to use original game model!