Skip to content

Commit 226e1d0

Browse files
authored
Basic course images (#8)
* Basic course images * Configuration for media
1 parent 2bb5d7a commit 226e1d0

File tree

11 files changed

+321
-120
lines changed

11 files changed

+321
-120
lines changed

3rdparty/python/default.lock

+187-85
Large diffs are not rendered by default.

3rdparty/requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ django-registration
66
django-bootstrap5
77
django-allauth
88
django-simple-menu
9-
whitenoise
9+
whitenoise
10+
Pillow

src/ansible/playbook.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,6 @@
7676
CSRF_TRUSTED_ORIGINS: "https://srv501663.hstgr.cloud,https://leaderboard.coneheads.org"
7777
DATABASE_URL: "sqlite:///data/db.sqlite"
7878
SECRET_KEY_FILE: "/etc/SECRET_KEY"
79-
STATIC_ROOT: "/data/static"
79+
STATIC_ROOT: "/data/static"
80+
MEDIA_ROOT: "/data/media"
81+
MEDIA_URL: "/media/"

src/python/gk/django/service.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ def run_manage(self):
3131
args = sys.argv
3232
if len(sys.argv) == 2 and sys.argv[1] == "runserver":
3333
# We rely on Pants's reloading, so turn off Django's (which doesn't interact
34-
# well with Pex: Pex's re-exec logic causes Django's re-exec to misdetect
34+
# well with Pex: Pex's re-exec logic causes Django's re-exec to misdetect
3535
# its entry point, see https://code.djangoproject.com/ticket/32314).
36-
# TODO: Some way to detect that we're in a `./pants run`, and only set
36+
# TODO: Some way to detect that we're in a `./pants run`, and only set
3737
# --noreload
3838
# in that case, so that users can run manage.py directly if they want to.
3939
args += ["--noreload"]

src/python/gk/leaderboard/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ python_sources(
22
dependencies=[
33
"src/python/gk/leaderboard/migrations",
44
"src/python/gk/leaderboard/templates",
5+
"3rdparty:requirements.txt#Pillow",
56
]
67
)
78

src/python/gk/leaderboard/admin.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ class SeriesAdmin(admin.ModelAdmin):
1313

1414

1515
class CourseAdmin(admin.ModelAdmin):
16-
fields = ["name", "slug", "series"]
16+
fieldsets = [
17+
(None, {"fields": ["name", "slug", "series"]}),
18+
("Images", {"fields": ["layout_image", "route_image"]}),
19+
]
1720
list_filter = ["series"]
1821
list_display = ("name", "slug", "series", "create_date", "modify_date")
1922

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Generated by Django 5.0.3 on 2024-03-31 21:05
2+
3+
import gk.leaderboard.models
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("leaderboard", "0004_series_course_create_date_course_modify_date_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name="course",
15+
options={
16+
"ordering": [
17+
models.OrderBy(models.F("series__order")),
18+
models.OrderBy(models.F("series__create_date"), descending=True),
19+
models.OrderBy(models.F("create_date"), descending=True),
20+
]
21+
},
22+
),
23+
migrations.AlterModelOptions(
24+
name="series",
25+
options={"ordering": ["-order"]},
26+
),
27+
migrations.AddField(
28+
model_name="course",
29+
name="layout_image",
30+
field=models.ImageField(
31+
blank=True, null=True, upload_to=gk.leaderboard.models._layout_path
32+
),
33+
),
34+
migrations.AddField(
35+
model_name="course",
36+
name="route_image",
37+
field=models.ImageField(
38+
blank=True, null=True, upload_to=gk.leaderboard.models._route_path
39+
),
40+
),
41+
]

src/python/gk/leaderboard/models.py

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os.path
2+
13
from django.conf import settings
24
from django.db import models
35
from django.db.models import F, Window
@@ -25,6 +27,14 @@ def expanded(self):
2527
return True
2628

2729

30+
def _layout_path(i, f):
31+
return f"{i._file_root()}/layout{os.path.splitext(f)[1]}"
32+
33+
34+
def _route_path(i, f):
35+
return f"{i._file_root()}/route{os.path.splitext(f)[1]}"
36+
37+
2838
class Course(models.Model):
2939
name = models.CharField(max_length=50)
3040
slug = models.SlugField()
@@ -34,6 +44,9 @@ class Course(models.Model):
3444
create_date = models.DateField(auto_now_add=True)
3545
modify_date = models.DateTimeField(auto_now=True)
3646

47+
layout_image = models.FileField(blank=True, null=True, upload_to=_layout_path)
48+
route_image = models.FileField(blank=True, null=True, upload_to=_route_path)
49+
3750
class Meta:
3851
unique_together = [
3952
("series", "slug"),
@@ -44,6 +57,9 @@ class Meta:
4457
F("create_date").desc(),
4558
]
4659

60+
def _file_root(self):
61+
return f"course/{self.series.slug if self.series else '_'}/{self.slug}"
62+
4763
def __str__(self):
4864
return f"{self.series}/{self.name}" if self.series else self.name
4965

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends "base.html" %}
2-
{% load i18n %}
2+
{% load i18n static %}
33

44
{% block head_title %}{% if course.series %}{{ course.series }} - {% endif %}{{ course }} | {{ block.super }}{% endblock %}
55

@@ -16,39 +16,67 @@ <h1>
1616

1717
</h1>
1818

19-
{% if best_times %}
20-
<h2>
21-
{% trans "Best Times" %}
22-
</h2>
19+
<div class="row">
2320

24-
{% include "includes/top_riders_table.html" with times=best_times %}
21+
<h2>
22+
{% trans "Best Times" %}
23+
</h2>
2524

26-
{% else %}
27-
<p>{% trans "There are no times for this course." %}</p>
28-
{% endif %}
25+
<div class="col-md-10">
26+
27+
{% if best_times %}
28+
{% include "includes/top_riders_table.html" with times=best_times %}
29+
{% else %}
30+
<p>{% trans "There are no times for this course." %}</p>
31+
{% endif %}
32+
</div>
33+
<div class="col-md-2">
34+
<div class="row">
2935

36+
{% if course.route_image %}
37+
<div class="col-6 col-md-12">
38+
<h4>Route</h4>
39+
<a href='{% get_media_prefix %}{{ course.route_image }}'>
40+
<img class="img-fluid img-thumbnail" src="{% get_media_prefix %}{{ course.route_image }}" alt="Route Image" />
41+
</a>
42+
</div>
43+
{% endif %}
44+
45+
{% if course.layout_image %}
46+
<div class="col-6 col-md-12">
47+
<h4>Layout</h4>
48+
<a href='{% get_media_prefix %}{{ course.layout_image }}'>
49+
<img class="img-fluid img-thumbnail" src="{% get_media_prefix %}{{ course.layout_image }}" alt="Layout Image" />
50+
</a>
51+
</div>
52+
{% endif %}
53+
</div>
54+
</div>
55+
</div>
3056

3157
{% if history %}
32-
<h2>{% trans "History" %}</h2>
33-
<table class="table table-striped table-sm mb-5">
34-
<thead>
35-
<th width="20%">{% trans "Date" %}</th>
36-
<th width="auto">{% trans "Rider" %}</th>
37-
<th width="10%">{% trans "Bike" %}</th>
38-
<th scope="col" width="5%">{% trans "Penalty" %}</th>
39-
<th width="10%" class="text-end">{% trans "Time" %}</th>
40-
</thead>
41-
42-
{% for time in history %}
43-
<tr>
44-
<td>{{ time.run_date }}</td>
45-
<td>{{ time.display_name }}</td>
46-
<td>{{ time.bike }}</td>
47-
<td class="text-danger text-end">{{ time.penalty }}</td>
48-
<td class="text-end">{{ time.time }}</td>
49-
</tr>
50-
{% endfor %}
51-
</table>
58+
<div class="row"><div class="col-12">
59+
<h2>{% trans "History" %}</h2>
60+
<table class="table table-striped table-sm mb-5">
61+
<thead>
62+
<th width="20%">{% trans "Date" %}</th>
63+
<th width="auto">{% trans "Rider" %}</th>
64+
<th width="10%">{% trans "Bike" %}</th>
65+
<th scope="col" width="5%">{% trans "Penalty" %}</th>
66+
<th width="10%" class="text-end">{% trans "Time" %}</th>
67+
</thead>
68+
69+
{% for time in history %}
70+
<tr>
71+
<td>{{ time.run_date }}</td>
72+
<td>{{ time.display_name }}</td>
73+
<td>{{ time.bike }}</td>
74+
<td class="text-danger text-end">{{ time.penalty }}</td>
75+
<td class="text-end">{{ time.time }}</td>
76+
</tr>
77+
{% endfor %}
78+
</table>
79+
</div></div>
5280
{% endif %}
5381

5482
{% endblock %}

src/python/gk/web/settings.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@
9494
WSGI_APPLICATION = "gk.django.wsgi.application"
9595

9696
STATIC_ROOT = env("STATIC_ROOT", default=None)
97-
MEDIA_ROOT = env("MEDIA_ROOT", default=None)
97+
MEDIA_ROOT = env("MEDIA_ROOT", default=".data/media/")
98+
MEDIA_URL = env("MEDIA_URL", default="/media/")
9899

99100
STATICFILES_DIRS = [BASE_DIR / "web/static"]
100101

src/python/gk/web/urls.py

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1. Import the include() function: from django.urls import include, path
1414
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1515
"""
16+
from django.conf import settings
1617
from django.contrib import admin
1718
from django.urls import include, path
1819
from gk.leaderboard import views as leaderboard
@@ -36,3 +37,8 @@
3637
path("tos/", web.TermsOfServiceView.as_view(), name="terms-of-service"),
3738
path("privacy/", web.PrivacyPolicyView.as_view(), name="privacy-policy"),
3839
]
40+
41+
if settings.DEBUG:
42+
from django.conf.urls.static import static
43+
44+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

0 commit comments

Comments
 (0)