From 03c2d72ed24b9facbf30f15b265ae4aa504ecc9b Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Mon, 13 Feb 2023 00:19:23 +0000 Subject: [PATCH 1/6] Add current, recent and soon events to homepage. Also only show leagues which are currently taking place. --- core/templates/index.html | 45 +++++++++++++++++++++++++++++++++++---- core/views.py | 18 ++++++++++++++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/core/templates/index.html b/core/templates/index.html index eb4cd9a..7adefe1 100644 --- a/core/templates/index.html +++ b/core/templates/index.html @@ -9,15 +9,52 @@

TamlynScore

TamlynScore is an online platform for running archery competitions.
If you'd like to use TamlynScore for your event, please contact us.

+

Tournaments

+
+ {% if current_competitions %} +
+

In progress and coming soon

+ +
+ {% endif %} + {% if recent_competitions %} +
+

Recently Finished

+ +
+ {% endif %} +
+ +{% if seasons %}

Leagues

-{% for league in leagues %} - +{% for season in seasons %} +
- -

{{ league }}

+ +

{{ season.league }}

{% endfor %}
+{% endif %} + {% endblock %} diff --git a/core/views.py b/core/views.py index a2833c8..fb5725e 100644 --- a/core/views.py +++ b/core/views.py @@ -1,9 +1,11 @@ import copy +import datetime from django.core.cache import cache from django.db.models import Prefetch from django.shortcuts import get_object_or_404 from django.urls import reverse +from django.utils import timezone from django.views.generic import ( CreateView, DetailView, ListView, TemplateView, UpdateView, ) @@ -12,7 +14,7 @@ from entries.models import Competition, CompetitionEntry from entries.views import BatchEntryMixin -from leagues.models import League +from leagues.models import Season from scores.models import Score from .forms import ArcherForm, ClubArcherForm @@ -41,7 +43,19 @@ class Index(TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['leagues'] = League.objects.order_by('?') + today = timezone.now().date() + + context['seasons'] = Season.objects.filter(start_date__lte=today, end_date__gt=today) + + competitions = Competition.objects.select_related('tournament').order_by('date') + context['current_competitions'] = competitions.filter( + date__lte=today + datetime.timedelta(days=7), + end_date__gte=today, + ) + context['recent_competitions'] = competitions.filter( + date__gte=today - datetime.timedelta(days=7), + end_date__lt=today, + ) return context From 142058cd9cc27d0854a61035413fc4f455fea2f3 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Mon, 13 Feb 2023 00:20:00 +0000 Subject: [PATCH 2/6] Make a start on better content of the competition home page. --- .../templates/entries/competition_detail.html | 93 +++++++++++++++---- .../templates/entries/competition_list.html | 6 +- entries/views.py | 21 +++++ tamlynscore/sass/_typography.sass | 6 ++ tamlynscore/static/css/main.css | 10 ++ 5 files changed, 118 insertions(+), 18 deletions(-) diff --git a/entries/templates/entries/competition_detail.html b/entries/templates/entries/competition_detail.html index d60bc07..e96fde1 100644 --- a/entries/templates/entries/competition_detail.html +++ b/entries/templates/entries/competition_detail.html @@ -4,7 +4,7 @@ {% if current %}
-

Tournaments in progress

+

Competitions in progress

@@ -25,15 +28,18 @@

Tournaments in progress

-

Upcoming tournaments

+

Upcoming competitions

@@ -41,17 +47,40 @@

Upcoming tournaments

{% if past %}
-

Completed tournaments

- +
+ {% endif %} +
+

{{ competition.date.year }}

+ +
{% endif %}
{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/tamlynscore/sass/components/_link_list.sass b/tamlynscore/sass/components/_link_list.sass index 2c26504..7348a09 100644 --- a/tamlynscore/sass/components/_link_list.sass +++ b/tamlynscore/sass/components/_link_list.sass @@ -19,3 +19,22 @@ ul.link-list &:hover background: $bg cursor: pointer + +.year-accordion + &__header + cursor: pointer + &::after + @include fa-icon() + content: $fa-var-chevron-right + margin-left: 5px + color: $borders + font-size: 14px + + .link-list + display: none + + &--active + .year-accordion__header::after + transform: rotate(90deg) + .link-list + display: block diff --git a/tamlynscore/static/css/main.css b/tamlynscore/static/css/main.css index 2b0bc68..1a2592b 100644 --- a/tamlynscore/static/css/main.css +++ b/tamlynscore/static/css/main.css @@ -4024,6 +4024,36 @@ ul.link-list li a:hover { cursor: pointer; } +/* line 24, ../../sass/components/_link_list.sass */ +.year-accordion__header { + cursor: pointer; +} +/* line 26, ../../sass/components/_link_list.sass */ +.year-accordion__header::after { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: ""; + margin-left: 5px; + color: #CCCCCC; + font-size: 14px; +} +/* line 33, ../../sass/components/_link_list.sass */ +.year-accordion .link-list { + display: none; +} +/* line 37, ../../sass/components/_link_list.sass */ +.year-accordion--active .year-accordion__header::after { + transform: rotate(90deg); +} +/* line 39, ../../sass/components/_link_list.sass */ +.year-accordion--active .link-list { + display: block; +} + /* line 1, ../../sass/components/_archer_block.sass */ .archer-block { font-size: 13px; From a2fa219dba8e7c6558bf876fc6bb25259ef2a6a6 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Sat, 8 Jul 2023 11:45:08 +0100 Subject: [PATCH 4/6] Add a summary of results to the home page --- .../templates/entries/competition_detail.html | 23 ++++++- entries/views.py | 14 +++- scores/mixins.py | 68 +++++++++++++++++++ scores/result_modes.py | 2 +- scores/views.py | 59 +--------------- 5 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 scores/mixins.py diff --git a/entries/templates/entries/competition_detail.html b/entries/templates/entries/competition_detail.html index e96fde1..5cb0bb8 100644 --- a/entries/templates/entries/competition_detail.html +++ b/entries/templates/entries/competition_detail.html @@ -91,11 +91,28 @@

Target List

{% else %} -

TODO! Pull the top results here - minimal team results (when teams exist), top 3 when ByRound exists, if neither exist then link to results

+ {% if by_round %} +
+

Results by round

+ + {% for round, categories in by_round.items %} + {% for category, scores in categories.items %} + + {% for score in scores|slice:":3" %} + + + + + + {% endfor %} + {% endfor %} + {% endfor %} +
{{ round }} - {{ category }}
{% if score.placing %}{{ score.placing }}{% endif %}{{ score.target.session_entry.competition_entry.archer }}{{ score.details.0 }}
+

Full results by round

+
+ {% endif %} {% endif %} -
-
{% if legs %}
{% if legs %} diff --git a/entries/views.py b/entries/views.py index 48bf803..8b4270e 100644 --- a/entries/views.py +++ b/entries/views.py @@ -28,6 +28,7 @@ from reportlab.rl_config import defaultPageSize from core.models import Archer +from scores.mixins import ResultModeMixin from .forms import ( ArcherSearchForm, CompetitionForm, CSVEntryForm, EntryCreateForm, @@ -92,13 +93,16 @@ def get_context_data(self, **kwargs): return context -class CompetitionDetail(CompetitionMixin, DetailView): +class CompetitionDetail(CompetitionMixin, ResultModeMixin, DetailView): admin_required = False object_name = 'competition' def get_object(self): return self.competition + def get_result_mode(self, mode): + pass + def get_context_data(self, **kwargs): today = timezone.now().date() @@ -118,6 +122,14 @@ def get_context_data(self, **kwargs): context['target_list_set'] = TargetAllocation.objects.filter( session_entry__competition_entry__competition=self.competition, ).exists() + else: + by_round = self.get_mode('by-round') + if by_round: + context['by_round'] = by_round.get_results(self.competition, self.get_scores(), leaderboard=True, request=self.request) + for section in context['by_round']: + for category in context['by_round'][section]: + for score in context['by_round'][section][category]: + score.details = by_round.score_details(score, section) return context diff --git a/scores/mixins.py b/scores/mixins.py new file mode 100644 index 0000000..80a4dcc --- /dev/null +++ b/scores/mixins.py @@ -0,0 +1,68 @@ +from django.http import Http404 + +from entries.models import ResultsMode + +from .models import Score +from .result_modes import get_mode + + +class ResultModeMixin(object): + include_distance_breakdown = False + format = None + + def get_hide_golds(self): + if self.format == 'big-screen': + return True + return False + + def get_scores(self): + scores = Score.objects.filter( + target__session_entry__competition_entry__competition=self.competition + ).select_related( + 'target', + 'target__session_entry', + 'target__session_entry__session_round', + 'target__session_entry__session_round__shot_round', + 'target__session_entry__competition_entry', + 'target__session_entry__competition_entry__competition', + 'target__session_entry__competition_entry__archer', + 'target__session_entry__competition_entry__bowstyle', + 'target__session_entry__competition_entry__club', + ).order_by( + '-target__session_entry__competition_entry__age', + '-target__session_entry__competition_entry__agb_age', + 'target__session_entry__competition_entry__novice', + 'target__session_entry__competition_entry__bowstyle', + 'target__session_entry__competition_entry__archer__gender', + 'disqualified', + '-score', + '-is_actual_zero', + '-golds', + '-xs' + ) + return scores + + def get_mode(self, mode_name=None, load=False): + if mode_name is None: + mode_name = self.kwargs['mode'] + mode = get_mode(mode_name, include_distance_breakdown=self.include_distance_breakdown, hide_golds=self.get_hide_golds()) + if not mode: + raise Http404('No such mode') + if load: + exists, obj = self.mode_exists(mode, load=True) + else: + exists = self.mode_exists(mode) + if not exists: + raise Http404('No such mode for this competition') + return (mode, obj) if load else mode + + def mode_exists(self, mode, load=False): + if load: + try: + obj = self.competition.result_modes.filter(mode=mode.slug).get() + except ResultsMode.DoesNotExist: + return False, None + else: + return True, obj + else: + return self.competition.result_modes.filter(mode=mode.slug).exists() diff --git a/scores/result_modes.py b/scores/result_modes.py index 27b5a7d..1af0915 100644 --- a/scores/result_modes.py +++ b/scores/result_modes.py @@ -185,7 +185,7 @@ def score_details(self, score, section): if score.disqualified or score.target.session_entry.session_round.session.scoring_system == SCORING_TOTALS: scores += [''] * len(subrounds) elif not hasattr(score, 'source'): - scores += subrounds + scores += [''] * len(subrounds) else: score = score.source subround_scores = [] diff --git a/scores/views.py b/scores/views.py index 28fea87..272062e 100644 --- a/scores/views.py +++ b/scores/views.py @@ -28,6 +28,7 @@ from olympic.models import OlympicSessionRound from .forms import get_arrow_formset, get_dozen_formset +from .mixins import ResultModeMixin from .models import Arrow, Dozen, Score from .result_modes import ByRound, get_mode @@ -486,35 +487,6 @@ def render_to_json(self, context): return HttpResponse(results, content_type='application/json') -class ResultModeMixin(object): - def get_scores(self): - scores = Score.objects.filter( - target__session_entry__competition_entry__competition=self.competition - ).select_related( - 'target', - 'target__session_entry', - 'target__session_entry__session_round', - 'target__session_entry__session_round__shot_round', - 'target__session_entry__competition_entry', - 'target__session_entry__competition_entry__competition', - 'target__session_entry__competition_entry__archer', - 'target__session_entry__competition_entry__bowstyle', - 'target__session_entry__competition_entry__club', - ).order_by( - '-target__session_entry__competition_entry__age', - '-target__session_entry__competition_entry__agb_age', - 'target__session_entry__competition_entry__novice', - 'target__session_entry__competition_entry__bowstyle', - 'target__session_entry__competition_entry__archer__gender', - 'disqualified', - '-score', - '-is_actual_zero', - '-golds', - '-xs' - ) - return scores - - class Leaderboard(ResultModeMixin, CompetitionMixin, PDFResultsRenderer, CSVResultsRenderer, JSONResultsRenderer, ListView): """General leaderboard/results generation. @@ -529,7 +501,6 @@ class Leaderboard(ResultModeMixin, CompetitionMixin, PDFResultsRenderer, CSVResu leaderboard = True title = 'Leaderboard' url_name = 'leaderboard' - include_distance_breakdown = False def get(self, request, *args, **kwargs): self.format = self.get_format() @@ -547,29 +518,6 @@ def post(self, request, *args, **kwargs): obj.save() return HttpResponseRedirect(request.get_full_path()) - def get_mode(self, load=False): - mode = get_mode(self.kwargs['mode'], include_distance_breakdown=self.include_distance_breakdown, hide_golds=self.get_hide_golds()) - if not mode: - raise Http404('No such mode') - if load: - exists, obj = self.mode_exists(mode, load=True) - else: - exists = self.mode_exists(mode) - if not exists: - raise Http404('No such mode for this competition') - return (mode, obj) if load else mode - - def mode_exists(self, mode, load=False): - if load: - try: - obj = self.competition.result_modes.filter(mode=mode.slug).get() - except ResultsMode.DoesNotExist: - return False, None - else: - return True, obj - else: - return self.competition.result_modes.filter(mode=mode.slug).exists() - def get_format(self): format = self.kwargs['format'] if format not in ['html', 'pdf', 'pdf-summary', 'big-screen', 'csv', 'json']: @@ -587,11 +535,6 @@ def get_context_data(self, **kwargs): kwargs['leaderboard'] = self.leaderboard return super(Leaderboard, self).get_context_data(**kwargs) - def get_hide_golds(self): - if self.format == 'big-screen': - return True - return False - def render_to_response(self, context, **response_kwargs): results = context['results'] if self.format == 'pdf': From b276d439638f17f13e474276e2c76d0e9e574975 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Sat, 8 Jul 2023 11:58:38 +0100 Subject: [PATCH 5/6] Allow competitions to override their tournament name --- ...0034_competition_name_override_and_more.py | 28 +++++++++++++++++++ entries/models.py | 14 ++++++++-- .../templates/entries/competition_detail.html | 10 +++++-- 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 entries/migrations/0034_competition_name_override_and_more.py diff --git a/entries/migrations/0034_competition_name_override_and_more.py b/entries/migrations/0034_competition_name_override_and_more.py new file mode 100644 index 0000000..6ce573f --- /dev/null +++ b/entries/migrations/0034_competition_name_override_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.1.10 on 2023-07-08 10:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('entries', '0033_competition_split_categories_on_agb_age'), + ] + + operations = [ + migrations.AddField( + model_name='competition', + name='name_override', + field=models.CharField(blank=True, default='', help_text='Override the recurring tournament name', max_length=200), + ), + migrations.AddField( + model_name='competition', + name='short_name_override', + field=models.CharField(blank=True, default='', max_length=20), + ), + migrations.AlterField( + model_name='competition', + name='slug', + field=models.SlugField(unique=True), + ), + ] diff --git a/entries/models.py b/entries/models.py index fbea350..4efad68 100644 --- a/entries/models.py +++ b/entries/models.py @@ -70,12 +70,14 @@ class Meta: class Competition(ResultsFormatFields, models.Model): tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE) + name_override = models.CharField(max_length=200, blank=True, default='', help_text='Override the recurring tournament name') + short_name_override = models.CharField(max_length=20, blank=True, default='') admins = models.ManyToManyField('core.User', blank=True) date = models.DateField() end_date = models.DateField(blank=True, null=True) - slug = models.SlugField(editable=False, unique=True) + slug = models.SlugField(unique=True) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) @@ -89,7 +91,15 @@ def get_absolute_url(self): return reverse('competition_detail', kwargs={'slug': self.slug}) def __str__(self): - return u'{0} {1}'.format(self.tournament, self.date.year) + return u'{0} {1}'.format(self.short_name, self.date.year) + + @property + def full_name(self): + return self.name_override or self.tournament.full_name + + @property + def short_name(self): + return self.short_name_override or self.tournament.short_name def clean(self, *args, **kwargs): if self.end_date is None: diff --git a/entries/templates/entries/competition_detail.html b/entries/templates/entries/competition_detail.html index 5cb0bb8..2c147ef 100644 --- a/entries/templates/entries/competition_detail.html +++ b/entries/templates/entries/competition_detail.html @@ -3,7 +3,7 @@ {% block precontent %}