Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
426c8cd
[IF] add twine file name and content for database storage
zaynacheema May 21, 2025
957393d
[Interactive fiction] to get twine from the database for viewing
zaynacheema May 21, 2025
ecb9e8e
[Interactive fiction] make sure the serve_twine_from_db view is acces…
zaynacheema May 21, 2025
a081cd6
[Interactive Fiction] Add play button if the twine game is from the d…
zaynacheema May 21, 2025
dd04cd0
[Interactive Fiction] Make sure twine file is binary field not file f…
zaynacheema May 21, 2025
fb374be
[Interactive Fiction] update form handling in game create view
zaynacheema May 21, 2025
668be7a
[Interactive Fiction] Fix error as the file is a binary field now
zaynacheema May 21, 2025
2ce0f25
[Interactive Fiction] Update game_form.html with Twine upload
zaynacheema May 21, 2025
b7ad807
[Interactive Fiction] Fix the name error by not using the uploaded file
zaynacheema May 21, 2025
6a3c94d
[Interactive fiction] fix byte reading in form_valid
zaynacheema May 21, 2025
4e532c2
[Interactive fiction] fix byte reading error in form_valid
zaynacheema May 21, 2025
925a565
[Interactive Fiction] Changing order for file handling in form_valid
zaynacheema May 21, 2025
87c1375
[IF] rename variable
zaynacheema May 21, 2025
8e8399c
[IF] fix char versus binary field reading
zaynacheema May 21, 2025
2c70f21
[IF] update variable name
zaynacheema May 21, 2025
a7722c6
[IF] fix variable name inconsistency for twine_file
zaynacheema May 21, 2025
c51f955
[IF] revert variable name to avoid naming inconsistency
zaynacheema May 21, 2025
819e992
[IF] fix file handling based on update game model
zaynacheema May 21, 2025
931a566
[Interactive fiction] update variable name based on model change
zaynacheema May 21, 2025
7910929
[IF] fix string error from changing file field to binary field in mod…
zaynacheema May 21, 2025
4722e38
[Interactive fiction] clean up twine file handling
zaynacheema May 21, 2025
7acb811
[IF] force migration check by removing, will add this back
zaynacheema May 21, 2025
a332b75
[IF] add it back
zaynacheema May 21, 2025
8ec00ad
Fix migrations
May 21, 2025
c88fe3e
Merge branch 'dev' into interactive-fiction/database-storing
zaynacheema May 21, 2025
c7d42be
[IF] merge migrations
May 21, 2025
493eecf
ran pre commit fixes
May 21, 2025
7ab64d4
[IF] Fix migration
May 21, 2025
784bd94
resolve merge conflict
May 21, 2025
8565eef
Merge branch 'dev' into interactive-fiction/database-storing
May 21, 2025
490eb20
[IF] Run pre-commit hooks and finalize test fixes
May 23, 2025
51e50d1
Merge branch 'dev' into interactive-fiction/database-storing
zaynacheema May 23, 2025
c27fed1
[IF] Merge the conflict and making migrations
May 23, 2025
48639a2
Fix migrations and restore working environment
May 23, 2025
494ad2c
Add dummy migration to bypass missing forum_conversation dependency
May 23, 2025
3c01eee
Add fake migration to satisfy missing forum_conversation.0010 dependency
May 23, 2025
7ed8cfb
Add missing __init__.py to forum/migrations to complete migration fix
May 23, 2025
65ffd8b
Ensure forum_conversation migration 0010 is tracked and committed
May 23, 2025
3700b42
Remove incorrect chigame.forums.forum_conversation module
May 23, 2025
f856ec5
[Revert] Restore forum_conversation and migration file
May 25, 2025
4a90f26
Merge branch 'dev' into interactive-fiction/database-storing
zaynacheema May 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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


Expand Down
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",
Copy link
Copy Markdown
Contributor

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!

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",
),
]
17 changes: 17 additions & 0 deletions src/chigame/games/migrations/0043_remove_game_twine_file_name.py
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",
),
]
18 changes: 18 additions & 0 deletions src/chigame/games/migrations/0044_game_twine_file_name.py
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):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of migrations, I wonder if there is an ability in a further PR to make these cumulative.


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),
),
]
13 changes: 13 additions & 0 deletions src/chigame/games/migrations/0045_merge_20250521_1434.py
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 = []
5 changes: 4 additions & 1 deletion src/chigame/games/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ class Game(models.Model):
max_players = models.PositiveIntegerField()

# interactive fiction - twine file
twine_file = models.FileField(upload_to="twine_games/", null=True, blank=True)
# twine_file = models.FileField(upload_to="twine_games/", null=True, blank=True)
# want twine file to be binary to store in the actual database
twine_file_name = models.CharField(max_length=255, null=True, blank=True)
twine_file = models.BinaryField(null=True, blank=True)

suggested_age = models.PositiveSmallIntegerField(
null=True, blank=True
Expand Down
1 change: 1 addition & 0 deletions src/chigame/games/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
path("interactive-fiction/", views.InteractiveFictionView.as_view(), name="interactive-fiction"),
path("<int:pk>/upload/", UploadFileView.as_view(), name="upload-file"),
path("if-game/<int:pk>/", InteractiveFictionView.as_view(), name="interactive-fiction-detail"),
path("twine-db/<int:pk>/", views.serve_twine_from_db, name="twine-db"),
# tournaments
path("tournaments/", views.TournamentListView.as_view(), name="tournament-list"),
path("tournaments/<int:pk>/", views.TournamentDetailView.as_view(), name="tournament-detail"),
Expand Down
32 changes: 25 additions & 7 deletions src/chigame/games/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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})

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
142 changes: 142 additions & 0 deletions src/chigame/leaderboards/migrations/0001_initial.py
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")},
},
),
]
Empty file.
4 changes: 2 additions & 2 deletions src/templates/games/game_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ <h3 id="description">Description</h3>
</p>
{% endif %}
<p>{{ game.description|linebreaksbr }}</p>
{% if is_twine_game %}
{% if game.twine_file %}
<h3>Play Twine Game</h3>
<a href="{{ game.twine_file.url }}"
<a href="{% url 'twine-db' game.pk %}"
target="_blank"
class="btn btn-success">Open Game in New Tab</a>
{% endif %}
Expand Down
5 changes: 5 additions & 0 deletions src/templates/games/game_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ <h1>
</div>
<br />
{% endfor %}
<!-- Twine upload (manual field) -->
<label for="id_twine_file">Upload Twine Game (.html):</label>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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) -->
Expand Down