diff --git a/Dockerfile b/Dockerfile index 58e008b7..3369c318 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ COPY --from=build --chown=django:django /root/.local /home/django/.local RUN apt-get update && apt-get install -y \ default-mysql-client \ + mime-support \ libmariadb3 USER django @@ -41,6 +42,5 @@ ENV PROJECT_ROOT=/srv ENV TEMPLATE_DIR=/srv/templates COPY --chown=django:django scripts/ agagd/ /srv/ -RUN SECRET_KEY=stub-for-build python manage.py collectstatic --noinput CMD ["/srv/entrypoint.sh"] diff --git a/agagd/agagd/settings/base.py b/agagd/agagd/settings/base.py index d20cf500..cda27ff9 100644 --- a/agagd/agagd/settings/base.py +++ b/agagd/agagd/settings/base.py @@ -79,10 +79,10 @@ STATICFILES_FINDERS = ( "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", - # 'django.contrib.staticfiles.finders.DefaultStorageFinder', + "django.contrib.staticfiles.finders.DefaultStorageFinder", ) -MIDDLEWARE = ( +MIDDLEWARE = [ "django.middleware.common.CommonMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -90,7 +90,7 @@ "django.contrib.messages.middleware.MessageMiddleware", # Uncomment the next line for simple clickjacking protection: "django.middleware.clickjacking.XFrameOptionsMiddleware", -) +] ROOT_URLCONF = "agagd.urls" @@ -123,11 +123,31 @@ # 'django.template.loaders.eggs.Loader', ], }, - } + }, + { + "BACKEND": "django.template.backends.jinja2.Jinja2", + "DIRS": [os.path.join(PROJECT_ROOT, "jinja2")], + "OPTIONS": { + "environment": "agagd_core.jinga2.environment", + "context_processors": [ + # Standard context_processors + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + # Custom context_processors + "django.template.context_processors.request", + "agagd_core.context_processors.google_analytics_tracking_id", + ], + }, + }, ] -INSTALLED_APPS = ( +INSTALLED_APPS = [ "agagd_core", "django.contrib.auth", "django.contrib.contenttypes", @@ -136,7 +156,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "django_tables2", -) +] # A sample logging configuration. The only tangible logging # performed by this configuration is to send an email to diff --git a/agagd/agagd/settings/prod.py b/agagd/agagd/settings/prod.py index 65917406..6cbe8692 100644 --- a/agagd/agagd/settings/prod.py +++ b/agagd/agagd/settings/prod.py @@ -4,9 +4,31 @@ if os.getenv("DEBUG") == "true": DEBUG = True + + # DebugToolbar Middleware + MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] + + # DebugToobar + INSTALLED_APPS += ["debug_toolbar"] + + # Add our Docker host IP to the INTERNAL_IPS + import os + import socket + + HOSTNAME = os.getenv("HOSTNAME") + INTERNAL_IPS = ["127.0.0.1", socket.gethostbyname(HOSTNAME)] + + # DebugToolbar Configurations + DEBUG_TOOLBAR_CONFIG = { + "INTERCEPT_REDIRECTS": False, + "SHOW_COLLAPSED": True, + "SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG, + } + else: DEBUG = False + GOOGLE_ANALYTICS_TRACKING_ID = os.getenv("GOOGLE_ANALYTICS_TRACKING_ID", "") ADMIN_ENABLED = False @@ -21,7 +43,6 @@ _dbname = os.getenv("APP_DB_NAME", "") _dbhost = os.getenv("DB_HOST", "") _dbport = os.getenv("DB_PORT", "") -_templates = os.getenv("TEMPLATE_DIR", os.path.join(PROJECT_ROOT, "templates")) SECRET_KEY = _key @@ -35,5 +56,3 @@ "PORT": _dbport, # Set to empty string for default. Not used with sqlite3. } } - -TEMPLATES[0]["DIRS"] = [_templates] diff --git a/agagd/agagd/urls.py b/agagd/agagd/urls.py index 2399751b..f292ca4c 100644 --- a/agagd/agagd/urls.py +++ b/agagd/agagd/urls.py @@ -7,7 +7,7 @@ from django.urls import path, reverse_lazy from django.views.generic import RedirectView -urlpatterns = ( +urlpatterns = [ url(r"^$", agagd_views.index, name="index"), url(r".php$", RedirectView.as_view(url=reverse_lazy("index"))), url(r"^search/$", agagd_views.search, name="search"), @@ -61,4 +61,10 @@ path("qualifications/", QualificationsPageView.as_view()), # Beta path("beta/", include(beta_urls.beta_patterns)), -) +] + +# DebugToolbar URL Configuration +if settings.DEBUG: + import debug_toolbar + + urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns diff --git a/agagd/agagd_core/jinga2.py b/agagd/agagd_core/jinga2.py new file mode 100644 index 00000000..d0096005 --- /dev/null +++ b/agagd/agagd_core/jinga2.py @@ -0,0 +1,28 @@ +import agagd_core.models as agagd_models +from django.core.exceptions import ObjectDoesNotExist +from django.templatetags.static import static +from django.urls import reverse +from jinja2 import Environment + + +def get_members_name_and_id(value): + """ + Provides jinja2 custom filter which returns + the members information as + "[member's full name] (member_id)". + """ + try: + member = agagd_models.Member.objects.values("full_name", "member_id").get( + pk=value + ) + except ObjectDoesNotExist: + raise ("Member {value} does not exist.") + + return f"{member['full_name']} ({member['member_id']})" + + +def environment(**options): + env = Environment(**options) + env.filters["get_members_name_and_id"] = get_members_name_and_id + env.globals.update({"static": static, "url": reverse}) + return env diff --git a/agagd/agagd_core/tables/beta.py b/agagd/agagd_core/tables/beta.py deleted file mode 100644 index 7aa43ca0..00000000 --- a/agagd/agagd_core/tables/beta.py +++ /dev/null @@ -1,362 +0,0 @@ -# AGAGD Models Imports -import agagd_core.models as agagd_models - -# Django Imports -import django_tables2 as tables -from django.core.exceptions import ObjectDoesNotExist -from django.urls import reverse -from django.utils.safestring import mark_safe - - -class WinnerColumn(tables.Column): - def __init__( - self, - color="W", - viewname=None, - urlconf=None, - args=None, - kwargs=None, - current_app=None, - attrs=None, - **extra - ): - super().__init__( - attrs=attrs, - linkify=dict( - viewname=viewname, - urlconf=urlconf, - args=args, - kwargs=kwargs, - current_app=current_app, - ), - **extra - ) - self.color = color - - def render(self, value, record): - if record.result == self.color: - self.attrs["td"] = {"class": "winner"} - else: - self.attrs["td"] = {"class": "runner-up"} - return value - - -class ChapterColumn(tables.Column): - # Takes a chapter ID and produces an href with the chapter's name - def render(self, value): - try: - members_chapter = agagd_models.Chapters.objects.get(member_id=value) - - chapter_url = reverse( - viewname="chapter_detail", kwargs={"chapter_id": value} - ) - - chapter_name = members_chapter.name - if chapter_name is None or chapter_name == "": - chapter_name = members_chapter.code - - chapter_html = mark_safe( - "{}".format(chapter_url, chapter_name) - ) - except: - chapter_html = u"\u2014" - - return chapter_html - - -# Standard GameTable display as is on agagd.usgo.org and most pages -class GameTable(tables.Table): - game_date = tables.Column( - verbose_name="Date", - attrs={ - "th": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - "td": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - }, - ) - handicap = tables.Column( - attrs={ - "th": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - "td": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - } - ) - pin_player_1 = WinnerColumn( - color="W", - viewname="member_detail", - verbose_name="White", - kwargs={"member_id": tables.A("pin_player_1.member_id")}, - ) - pin_player_2 = WinnerColumn( - color="B", - viewname="member_detail", - verbose_name="Black", - kwargs={"member_id": tables.A("pin_player_2.member_id")}, - ) - tournament_code = tables.LinkColumn( - verbose_name="Tournament", - viewname="tournament_detail", - kwargs={"tourn_code": tables.A("tournament_code.tournament_code")}, - ) - - class Meta: - model = agagd_models.Game - attrs = {"class": "table", "thead": {"class": "thead-light"}} - fields = ( - "game_date", - "tournament_code", - "pin_player_1", - "pin_player_2", - "round", - "handicap", - "komi", - ) - sequence = fields - template_name = "django_tables2/bootstrap4.html" - - -# Alternative GameTable -# -# Displays GameTable without the Tournament Name Column -# -# References: -# GitHubIssue#20 -class SecondaryGameTable(GameTable): - tournament_code = None - - class Meta: - model = agagd_models.Game - fields = ( - "game_date", - "round", - "pin_player_1", - "pin_player_2", - "handicap", - "komi", - ) - sequence = fields - template_name = "django_tables2/bootstrap4.html" - - -class OpponentTable(tables.Table): - def __init__(self, qs, p1, *args, **kwargs): - self.this_player = p1 - tables.Table.__init__(self, qs) - - empty_text = "Opponent information couldn't be calculated" - opponent = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("opponent.member_id")} - ) - total = tables.Column(verbose_name="Games") - won = tables.Column(verbose_name="Won", default=0) - lost = tables.Column(verbose_name="Lost") - ratio = tables.Column( - verbose_name="Rate", default=0, empty_values=(-1,), orderable=False - ) - - def render_ratio(self, record): - return "{:.2f}".format(record["won"] / record["total"]) - - class Meta: - attrs = {"class": "table", "thead": {"class": "thead-light"}} - order_by = ("-total", "-won") - template_name = "django_tables2/bootstrap4.html" - - -class MemberTable(tables.Table): - member_id = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - chapter_id = ChapterColumn(verbose_name="Chapter") - players__rating = tables.Column(verbose_name="Rating") - country = tables.LinkColumn( - "country_detail", kwargs={"country_name": tables.A("country")} - ) - full_name = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - - class Meta: - model = agagd_models.Member - attrs = {"class": "table", "thead": {"class": "thead-light"}} - fields = ("full_name", "state", "players__rating", "renewal_due", "country") - sequence = ( - "full_name", - "players__rating", - "chapter_id", - "country", - "state", - "renewal_due", - "member_id", - ) - template_name = "django_tables2/bootstrap4.html" - - -class ChapterMemberTable(MemberTable): - class Meta: - model = agagd_models.Member - attrs = {"class": "table", "thead": {"class": "thead-light"}} - fields = ("full_name", "state", "players__rating", "renewal_due", "country") - sequence = ( - "full_name", - "players__rating", - "country", - "state", - "renewal_due", - "member_id", - ) - template_name = "django_tables2/bootstrap4.html" - - -class TopDanTable(tables.Table): - member_id = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - full_name = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - - class Meta: - model = agagd_models.TopDan - attrs = {"class": "table", "thead": {"class": "thead-light"}} - fields = ("member_id", "full_name", "rating") - sequence = fields - template_name = "django_tables2/bootstrap4.html" - - -class TopKyuTable(tables.Table): - member_id = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - full_name = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - - class Meta: - model = agagd_models.TopKyu - attrs = {"class": "table", "thead": {"class": "thead-light"}} - fields = ("member_id", "full_name", "rating") - sequence = fields - template_name = "django_tables2/bootstrap4.html" - - -class MostRatedGamesPastYearTable(tables.Table): - member_id = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - name = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - total = tables.Column() - - class Meta: - model = agagd_models.MostRatedGamesPastYear - attrs = {"class": "table", "thead": {"class": "thead-light"}} - fields = ("member_id", "name", "total") - sequence = fields - template_name = "django_tables2/bootstrap4.html" - - -class MostTournamentsPastYearTable(tables.Table): - member_id = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - name = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - - class Meta: - model = agagd_models.MostTournamentsPastYear - attrs = {"class": "table", "thead": {"class": "thead-light"}} - fields = ("member_id", "name", "total") - sequence = fields - template_name = "django_tables2/bootstrap4.html" - - -class AllPlayerRatingsTable(tables.Table): - full_name = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - member_id = tables.LinkColumn( - "member_detail", kwargs={"member_id": tables.A("member_id")} - ) - type = tables.Column() - players__rating = tables.Column() - chapter_id = ChapterColumn(verbose_name="Chapter") - state = tables.Column() - players__sigma = tables.Column(verbose_name="Sigma") - - class Meta: - attrs = {"class": "table", "thead": {"class": "thead-light"}} - fields = ( - "full_name", - "member_id", - "players__rating", - "players__sigma", - "type", - "chapter_id", - "state", - ) - sequence = fields - template_name = "django_tables2/bootstrap4.html" - - -class TournamentTable(tables.Table): - tournament_date = tables.Column( - verbose_name="Date", - attrs={ - "th": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - "td": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - }, - ) - tournament_code = tables.LinkColumn( - "tournament_detail", - verbose_name="Code", - kwargs={"tourn_code": tables.A("tournament_code")}, - ) - total_players = tables.Column( - verbose_name="# Players", - attrs={ - "th": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - "td": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - }, - ) - elab_date = tables.Column( - verbose_name="Rated", - attrs={ - "th": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - "td": {"class": "d-none d-lg-table-cell d-xl-table-cell"}, - }, - ) - - class Meta: - model = agagd_models.Tournament - attrs = { - "class": "table", - "thead": {"class": "thead-light"}, - "th": {"scope": "col"}, - } - fields = ( - "tournament_date", - "tournament_code", - "description", - "city", - "state", - "total_players", - "rounds", - "elab_date", - ) - sequence = fields - template_name = "django_tables2/bootstrap4.html" - - -class TournamentPlayedTable(tables.Table): - tournament = tables.LinkColumn( - "tournament_detail", kwargs={"tourn_code": tables.A("tournament.pk")} - ) - date = tables.Column(default="Unknown") - won = tables.Column(verbose_name="Won", default=0) - lost = tables.Column(verbose_name="Lost", default=0) - # Steve note for issue #122 here - - class Meta: - attrs = {"class": "table", "thead": {"class": "thead-light"}} - template_name = "django_tables2/bootstrap4.html" diff --git a/agagd/agagd_core/urls.py b/agagd/agagd_core/urls.py index 9064b6cb..67b58693 100644 --- a/agagd/agagd_core/urls.py +++ b/agagd/agagd_core/urls.py @@ -6,4 +6,6 @@ beta_patterns = ([ path('', beta.index, name='index'), path('players/', beta.list_all_players, name='players_list'), + path('tournaments/', beta.list_all_tournaments, name='tournaments_list'), + path('tournaments//', beta.tournament_detail, name='tournament_detail') ], 'beta') diff --git a/agagd/agagd_core/views/beta.py b/agagd/agagd_core/views/beta.py index c827953d..76c39ab6 100644 --- a/agagd/agagd_core/views/beta.py +++ b/agagd/agagd_core/views/beta.py @@ -4,9 +4,6 @@ # AGAGD Models Import import agagd_core.models as agagd_models -# AGAGD Tables Import -import agagd_core.tables.beta as agagd_tables - # Django Imports from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.db.models import F, Q @@ -34,37 +31,130 @@ def agagd_paginator_helper( def index(request): - game_list = agagd_models.Game.objects.filter( - game_date__gte=datetime.now() - timedelta(days=180) - ).order_by("-game_date") - table = agagd_tables.GameTable(game_list, prefix="games") - topDanList = agagd_models.TopDan.objects.values() - topDanTable = agagd_tables.TopDanTable(topDanList) - topKyuList = agagd_models.TopKyu.objects.values() - topKyuTable = agagd_tables.TopKyuTable(topKyuList) - mostRatedGamesPastYearList = agagd_models.MostRatedGamesPastYear.objects.values() - mostRatedGamesTable = agagd_tables.MostRatedGamesPastYearTable( - mostRatedGamesPastYearList + default_mobile_column_attrs = "d-none d-lg-table-cell d-xl-table-cell" + + latest_games_table_headers = { + "game_date": "Date", + "tournament_code_id": "Tournament Code", + "pin_player_1": "White", + "pin_player_2": "Black", + "handicap": "Handicap", + "komi": "Komi", + } + + latest_games_mobile_columns = { + "handicap": default_mobile_column_attrs, + "komi": default_mobile_column_attrs, + } + + latest_games = agagd_models.Game.objects.values( + "game_date", + "tournament_code_id", + "pin_player_1", + "pin_player_2", + "handicap", + "komi", + ).order_by("-game_date")[:25] + + latest_tournaments_table_headers = { + "tournament_date": "Date", + "elab_date": "Rated", + "tournament_code": "Tournament Code", + "city": "City", + "state": "State", + "rounds": "Rounds", + } + + latest_tournaments_mobile_columns = { + "city": default_mobile_column_attrs, + "state": default_mobile_column_attrs, + "rounts": default_mobile_column_attrs, + } + + latest_tournaments = agagd_models.Tournament.objects.values( + "tournament_date", "elab_date", "tournament_code", "city", "state", "rounds" + ).order_by("-elab_date")[:25] + + top_10_kyu_dan_table_headers = { + "pin_player": "Player", + "rating": "Rating", + "sigma": "Sigma", + } + + top_10_dan_kyu = agagd_models.Players.objects.values( + "pin_player", "rating", "sigma" ) - mostTournamentsPastYearList = agagd_models.MostTournamentsPastYear.objects.values() - mostTournamentsPastYearTable = agagd_tables.MostTournamentsPastYearTable( - mostTournamentsPastYearList + + top_10_dan = top_10_dan_kyu.filter(rating__gt=0).order_by("-rating")[:10] + + top_10_kyu = top_10_dan_kyu.filter(rating__lt=0).order_by("-rating")[:10] + + return render( + request, + "beta.index.html", + { + "latest_games": latest_games, + "latest_tournaments": latest_tournaments, + "top_10_dan": top_10_dan, + "top_10_kyu": top_10_kyu, + "latest_games_table_headers": latest_games_table_headers, + "latest_tournaments_table_headers": latest_tournaments_table_headers, + "top_10_kyu_dan_table_headers": top_10_kyu_dan_table_headers, + "latest_games_mobile_columns": latest_games_mobile_columns, + "latest_tournaments_mobile_columns": latest_tournaments_mobile_columns, + }, ) - RequestConfig(request).configure(table) - tourneys = agagd_models.Tournament.objects.all().order_by("-tournament_date") - t_table = agagd_tables.TournamentTable(tourneys, prefix="tourneys") - RequestConfig(request, paginate={"per_page": 10}).configure(t_table) + + +def tournament_detail(request, code): + try: + tournament = agagd_models.Tournament.objects.get(pk=code) + except Tournament.DoesNotExist: + raise Http404(f"Tournament {name} does not exist.") + + tournament_information = { + "tournament_code": tournament.pk, + "description": tournament.description, + "elab_date": tournament.elab_date, + "tournament_date": tournament.tournament_date, + "state": tournament.state, + } + + tournament_table_headers = { + "tournament_code": "Code", + "description": "Description", + "tournament_date": "Date", + "elab_date": "Rated", + "city": "City", + "state": "State", + "rounds": "Rounds", + "total_players": "No. Players", + } + + tournament_games = agagd_paginator_helper( + request, + tournament.games_in_tourney.values( + "game_date", "pin_player_1", "pin_player_2", "handicap", "komi" + ), + ) + + tournament_game_table_headers = { + "game_date": "Date", + "pin_player_1": "White", + "pin_player_2": "Black", + "handicap": "Handicap", + "komi": "Komi", + } return render( request, - "agagd_core/index.beta.html", + "beta.tournament_detail.html", { - "table": table, - "top_dan_table": topDanTable, - "top_kyu_table": topKyuTable, - "most_rated_games_table": mostRatedGamesTable, - "most_tournaments_table": mostTournamentsPastYearTable, - "tournaments": t_table, + "page_title": tournament_information["tournament_code"], + "tournament_information": tournament_information, + "tournament_games": tournament_games, + "tournament_table_headers": tournament_table_headers, + "tournament_game_table_headers": tournament_game_table_headers, }, ) @@ -108,7 +198,7 @@ def list_all_players(request): return render( request, - "agagd_core/players_list.html", + "beta.players_list.html", { "mobile_column_attrs": mobile_column_attrs, "list_all_players_columns": list_all_players_columns, @@ -116,3 +206,50 @@ def list_all_players(request): "page_title": "Members Ratings", }, ) + + +def list_all_tournaments(request): + mobile_column_default_attrs = "d-none d-lg-table-cell d-xl-table-cell" + + mobile_columns = { + "city": mobile_column_default_attrs, + "state": mobile_column_default_attrs, + "total_players": mobile_column_default_attrs, + } + + table_headers = { + "tournament_code": "Code", + "description": "Description", + "tournament_date": "Date", + "elab_date": "Rated", + "city": "City", + "state": "State", + "rounds": "Rounds", + "total_players": "No. Players", + } + + list_all_tournaments_query = agagd_models.Tournament.objects.values( + "tournament_code", + "description", + "tournament_date", + "elab_date", + "city", + "state", + "rounds", + "total_players", + ).order_by("-tournament_date") + + list_all_tournaments_with_pagination = agagd_paginator_helper( + request, list_all_tournaments_query + ) + + return render( + request, + "beta.tournaments_list.html", + { + "mobile_columns": mobile_columns, + "table_headers": table_headers, + "list_all_tournaments": list_all_tournaments_with_pagination, + "page_title": "Tournaments", + }, + ) diff --git a/agagd/templates/base.beta.html b/agagd/jinja2/beta.base.html similarity index 86% rename from agagd/templates/base.beta.html rename to agagd/jinja2/beta.base.html index 8bca611d..4a308c22 100644 --- a/agagd/templates/base.beta.html +++ b/agagd/jinja2/beta.base.html @@ -1,13 +1,10 @@ - -{% load static %} - {% block title %} {% if page_title %} - {{ "AGAGD | "|add:page_title }} + {{ "AGAGD | " ~ page_title }} {% else %} AGAGD | American Go Game Database {% endif %} @@ -44,7 +41,9 @@ <header class="header row mx-0"> <div class="col-12 col-md-6"> <h4 class="header-subtext mb-0"> - {% block header_title %}AGAGD{% endblock header_title %} + {% block header_title %} + <a href="{{ url("beta:index") }}">AGAGD</a> + {% endblock header_title %} </h4> <h6 class="header-subtext"> @@ -53,7 +52,7 @@ <h6 class="header-subtext"> </div> <div class="col-12 col-md-6"> - <form class="form-inline justify-content-md-end my-2 my-lg-0" action="{% url "search" %}" method="get"> + <form class="form-inline justify-content-md-end my-2 my-lg-0" action="{{ url("search") }}" method="get"> <input class="form-control mr-sm-2" type="search" name="q" placeholder="Player Name or AGA ID" aria-label="Search"> </form> </div> @@ -72,7 +71,7 @@ <h6 class="header-subtext"> <h5>Ratings</h5> <ul class="list-unstyled"> <li> - <a href="{% url 'beta:players_list' %}">Members Ratings</a> + <a href="{{ url("beta:players_list") }}">Members Ratings</a> </li> <li> <a href="/information/">Information</a> @@ -83,6 +82,15 @@ <h5>Ratings</h5> </li> </ul> </div> + <div class="col-12 col-md-3"> + <h5>Tournaments</h5> + <ul class="list-unstyled"> + <li> + <a href="{{ url("beta:tournaments_list") }}">All</a> + </li> + </ul> + </div> + </section> </section> {% endblock footer_nav %} diff --git a/agagd/jinja2/beta.basic_table_dark.html b/agagd/jinja2/beta.basic_table_dark.html new file mode 100644 index 00000000..f83ae1c5 --- /dev/null +++ b/agagd/jinja2/beta.basic_table_dark.html @@ -0,0 +1,8 @@ +<table class="table"> + <thead class="thead-dark"> + {% block thead %}{% endblock %} + </thead> + <tbody> + {% block tbody %}{% endblock %} + </tbody> +</table> diff --git a/agagd/jinja2/beta.index.html b/agagd/jinja2/beta.index.html new file mode 100644 index 00000000..f991e941 --- /dev/null +++ b/agagd/jinja2/beta.index.html @@ -0,0 +1,103 @@ +{% extends "beta.base.html" %} + +{% block content %} + <section class="container"> + <section class="row top-players-tables"> + <section class="col-sm-12 col-md-12 col-lg-6 top-dan"> + <h5 class="table-headers top-players-headers">Top Dan</h5> + + <div class="table-headers top-dan-table"> + {% with table_headers=top_10_kyu_dan_table_headers, table_body_rows=top_10_dan %} + {% include "beta.top_10_dan_kyu.html" %} + {% endwith %} + </div> + </section> + + <section class="col-sm-12 col-md-12 col-lg-6 top-kyu"> + <h5 class="table-headers top-players-headers">Top Kyu</h5> + + <div class="top-kyu-table"> + {% with table_headers=top_10_kyu_dan_table_headers, table_body_rows=top_10_kyu %} + {% include "beta.top_10_dan_kyu.html" %} + {% endwith %} + + </div> + </section> + </section> + + <section class="row recent-activity-tables"> + <section class="col col-md-12 col-lg-12 col-xl-12 tournaments"> + <h5 class="table-headers top-players-headers">Tournaments</h5> + + <div class="tournaments-table"> + <table class="table"> + <thead class="thead-dark"> + <tr> + {% for key, value in latest_tournaments_table_headers.items() %} + <th scope="col" {% if latest_tournaments_mobile_columns[key] %} class="{{ latest_tournaments_mobile_columns[key] }}" {% endif %}> + {{ value }} + </th> + {% endfor %} + </tr> + </thead> + <tbody> + {% for row in latest_tournaments %} + <tr> + {% for key, value in row.items() %} + <td {% if latest_tournaments_mobile_columns[key] %} class="{{ latest_tournaments_mobile_columns[key] }}" {% endif %}> + + {% if key == "tournament_code" %} + <a href="{{ url("beta:tournament_detail", args=[value]) }}"> + {{ value }} + </a> + {% else %} + {{ value }} + {% endif %} + </td> + {% endfor %} + </tr> + {% endfor %} + </tbody> + </table> + </div> + </section> + + <section class="col col-md-12 col-lg-12 col-xl-12 games"> + <h5 class="table-headers top-players-headers">Games</h5> + + <div class="games-table"> + <table class="table"> + <thead class="thead-dark"> + <tr> + {% for key, value in latest_games_table_headers.items() %} + <th scope="col" {% if latest_games_mobile_columns[key] %} class="{{ latest_games_mobile_columns[key] }}" {% endif %}> + {{ value }} + </th> + {% endfor %} + </tr> + </thead> + <tbody> + {% for row in latest_games %} + <tr> + {% for key, value in row.items() %} + <td scope="col" {% if latest_games_mobile_columns[key] %} class="{{ latest_games_mobile_columns[key] }}" {% endif %}> + {% if key == "pin_player_1" or key == "pin_player_2" %} + <a href="{{ url("member_detail", args=[value]) }}">{{ value|get_members_name_and_id }}</a> + {% elif key == "tournament_code_id" %} + <a href="{{ url("beta:tournament_detail", args=[value]) }}"> + {{ value }} + </a> + {% else %} + {{ value }} + {% endif %} + </td> + {% endfor %} + </tr> + {% endfor %} + </tbody> + </table> + </div> + </section> + </section> + </section> +{% endblock %} diff --git a/agagd/templates/agagd_core/players_list.html b/agagd/jinja2/beta.players_list.html similarity index 62% rename from agagd/templates/agagd_core/players_list.html rename to agagd/jinja2/beta.players_list.html index 64072934..eca52d91 100644 --- a/agagd/templates/agagd_core/players_list.html +++ b/agagd/jinja2/beta.players_list.html @@ -1,11 +1,10 @@ -{% extends "base.beta.html" %} +{% extends "beta.base.html" %} {% block content %} <section id="list-all-players-block" class="container"> <section class="row"> <section class="col-sm-12 col-md-12 col-lg-12"> <h5 class="table-headers list-all-players-table-headers">Members Ratings</h5> - <div class="list-all-players-table"> <table class="table"> <thead class="thead-dark"> @@ -21,7 +20,7 @@ <h5 class="table-headers list-all-players-table-headers">Members Ratings</h5> {% for player_data in list_all_players_data %} <tr> <th scope="row"> - {{ forloop.counter0|add:list_all_players_data.start_index }} + {{ loop.index0 + list_all_players_data.start_index() }} </th> <td> <a href="/player/{{ player_data.member_id }}"> @@ -51,31 +50,10 @@ <h5 class="table-headers list-all-players-table-headers">Members Ratings</h5> </table> <nav aria-label="Table Page Navigation"> - - {% if list_all_players_data.has_other_pages %} - <ul class="pagination justify-content-center"> - {% if list_all_players_data.has_previous %} - <li class="previous page-item"> - <a href="?pg={{ list_all_players_data.previous_page_number }}" class="page-link">«</a> - </li> - {% endif %} - - {% for page_number in list_all_players_data.paginator.page_range %} - {% if page_number > list_all_players_data.number|add:'-5' and page_number < list_all_players_data.number|add:'5' %} - <li class="page-item {% if list_all_players_data.number == page_number %} active {% endif %}"> - <a href="?pg={{ page_number }}" class="page-link">{{ page_number }}</a> - </li> - {% endif %} - {% endfor %} - - {% if list_all_players_data.has_next %} - <li class="next page-item"> - <a href="?pg={{ list_all_players_data.next_page_number }}" class="page-link">»</a> - </li> - {% endif %} - </ul> - {% endif %} - </nav> + {% with table=list_all_players_data %} + {% include "beta.table_pagination.html" %} + {% endwith %} + </nav> </div> </section> </section> diff --git a/agagd/jinja2/beta.table_pagination.html b/agagd/jinja2/beta.table_pagination.html new file mode 100644 index 00000000..1faafd35 --- /dev/null +++ b/agagd/jinja2/beta.table_pagination.html @@ -0,0 +1,25 @@ +{% block table_pagination %} + {% if table.has_other_pages() %} + <ul class="pagination justify-content-center"> + {% if table.has_previous() %} + <li class="previous page-item"> + <a href="?pg={{ table.previous_page_number() }}" class="page-link">«</a> + </li> + {% endif %} + + {% for page_number in table.paginator.page_range %} + {% if page_number > (table.number - 5) and page_number < (table.number + 5) %} + <li class="page-item {% if table.number == page_number %} active {% endif %}"> + <a href="?pg={{ loop.index }}" class="page-link">{{ loop.index }}</a> + </li> + {% endif %} + {% endfor %} + + {% if table.has_next() %} + <li class="next page-item"> + <a href="?pg={{ table.next_page_number() }}" class="page-link">»</a> + </li> + {% endif %} + </ul> + {% endif %} +{% endblock %} diff --git a/agagd/jinja2/beta.top_10_dan_kyu.html b/agagd/jinja2/beta.top_10_dan_kyu.html new file mode 100644 index 00000000..0fd6dd77 --- /dev/null +++ b/agagd/jinja2/beta.top_10_dan_kyu.html @@ -0,0 +1,25 @@ +{% extends "beta.basic_table_dark.html" %} +{% block thead %} + <tr> + {% for key, value in table_headers.items() %} + <th scope="col"> + {{ value }} + </th> + {% endfor %} + </tr> +{% endblock %} +{% block tbody %} + {% for row in table_body_rows %} + <tr> + {% for key, value in row.items() %} + {% if key == "pin_player" %} + <td> + <a href="{{ url("member_detail", args=[value]) }}">{{ value|get_members_name_and_id }}</a> + </td> + {% else %} + <td>{{ value }}</td> + {% endif %} + {% endfor %} + </tr> + {% endfor %} +{% endblock %} diff --git a/agagd/jinja2/beta.tournament_detail.html b/agagd/jinja2/beta.tournament_detail.html new file mode 100644 index 00000000..b3abfd95 --- /dev/null +++ b/agagd/jinja2/beta.tournament_detail.html @@ -0,0 +1,70 @@ +{% extends "beta.base.html" %} + +{% block content %} + <section id="list-all-tournaments-block" class="container"> + <section class="row"> + <section class="col"> + <h5 class="table-headers tournament-name-header">{{ tournament_information.tournament_code }}</h5> + + <table class="table"> + <tbody> + {% for key, value in tournament_information.items() %} + <tr> + <th class="table-dark" scope="row"> + {% if key in tournament_table_headers %} + {{ tournament_table_headers[key] }} + {% else %} +   + {% endif %} + </th> + <td class="table-bordered">{{ value }}</td> + </tr> + {% endfor %} + </tbody> + </table> + </section> + </section> + <section class="row"> + <section class="col-sm-12 col-md-12 col-lg-12"> + <h5 class="table-headers list-all-tournament-players-table-header">Games</h5> + + <div class="list-all-tournament-players-table"> + <table class="table"> + <thead class="thead-dark"> + <tr> + {% for key, value in tournament_game_table_headers.items() %} + <th scope="col"> + {{ value }} + </th> + {% endfor %} + </tr> + </thead> + + <tbody> + {% for row in tournament_games %} + <tr> + {% for key, value in row.items() %} + + {% if key == "pin_player_1" or key == "pin_player_2" %} + <td> + <a href="/player/{{ value }}">{{ value|get_members_name_and_id }}</a> + </td> + {% else %} + <td>{{ value }}</td> + {% endif %} + {% endfor %} + </tr> + {% endfor %} + </tbody> + </table> + + <nav aria-label="Table Page Navigation"> + {% with table=tournament_games %} + {% include "beta.table_pagination.html" %} + {% endwith %} + </nav> + </div> + </section> + </section> + </section> +{% endblock %} diff --git a/agagd/jinja2/beta.tournaments_list.html b/agagd/jinja2/beta.tournaments_list.html new file mode 100644 index 00000000..43ce4a65 --- /dev/null +++ b/agagd/jinja2/beta.tournaments_list.html @@ -0,0 +1,47 @@ +{% extends "beta.base.html" %} + +{% block content %} + <section id="list-all-tournaments-block" class="container"> + <section class="row"> + <section class="col-sm-12 col-md-12 col-lg-12"> + <h5 class="table-headers list-all-tournaments-table-headers">{{ page_title }}</h5> + + <div class="list-all-tournaments-table"> + <table class="table"> + <thead class="thead-dark"> + <tr> + {% for key, value in table_headers.items() %} + <th scope="col" {% if mobile_columns[key] %} class="{{ mobile_columns[key] }}" {% endif %}> + {{ value }} + </th> + {% endfor %} + </tr> + </thead> + + <tbody> + {% for tournament_row in list_all_tournaments %} + <tr> + {% for key, value in tournament_row.items() %} + <td {% if mobile_columns[key] %} class="{{ mobile_columns[key] }}" {% endif %}> + {% if key == "tournament_code" %} + <a href="{{ url("beta:tournament_detail", args=[value]) }}">{{ value }}</a> + {% else %} + {{ value }} + {% endif %} + </td> + {% endfor %} + </tr> + {% endfor %} + </tbody> + </table> + + <nav aria-label="Table Page Navigation"> + {% with table=list_all_tournaments %} + {% include "beta.table_pagination.html" %} + {% endwith %} + </nav> + </div> + </section> + </section> + </section> +{% endblock %} diff --git a/agagd/templates/agagd_core/index.beta.html b/agagd/templates/agagd_core/index.beta.html deleted file mode 100644 index 7c08b929..00000000 --- a/agagd/templates/agagd_core/index.beta.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends "base.beta.html" %} -{% load render_table from django_tables2 %} - -{% block content %} - {{ block.super }} - <section class="container"> - <section class="row top-players-tables"> - <section class="col-sm-12 col-md-12 col-lg-6 top-dan"> - <h5 class="table-headers top-players-headers">Top Dan</h5> - - <div class="table-headers top-dan-table"> - {% render_table top_dan_table %} - </div> - </section> - - <section class="col-sm-12 col-md-12 col-lg-6 top-kyu"> - <h5 class="table-headers top-players-headers">Top Kyu</h5> - - <div class="top-kyu-table"> - {% render_table top_kyu_table %} - </div> - </section> - </section> - - <section class="row recent-activity-tables"> - <section class="col tournaments"> - <h5 class="table-headers top-players-headers">Tournaments</h5> - - <div class="tournaments-table"> - {% render_table tournaments %} - </div> - </section> - - <section class="col games"> - <h5 class="table-headers top-players-headers">Games</h5> - - <div class="games-table"> - {% render_table table %} - </div> - </section> - </section> - </section> -{% endblock %} diff --git a/docker-compose.yml b/docker-compose.yml index 9df069c9..89e26c68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,7 @@ services: - ./agagd/agagd_core:/srv/agagd_core - ./agagd/media:/srv/media - ./agagd/static:/srv/static + - ./agagd/jinja2:/srv/jinja2 - ./agagd/templates:/srv/templates db: diff --git a/requirements.txt b/requirements.txt index 15d93e39..98c4ade4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ Django==3.1.8 django-admin-tools==0.4.1 +django-debug-toolbar==3.2.1 django-tables2==2.3.0 +jinja2==2.11.3 PyMySQL==1.0.2 yolk==0.4.3 diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 0c00229d..c8f3f77a 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -24,10 +24,15 @@ function wait_for_db() { wait_for_db if $LOAD_FIXTURES == "true"; then - python make_fake_fixtures.py 100 1000 1000 > /tmp/fake_agagd_data.json + python make_fake_fixtures.py 1000 1000 1000 > /tmp/fake_agagd_data.json python manage.py loaddata /tmp/fake_agagd_data.json fi +# Run Collect Static in the Entrypoint because Dockerfile does not always +# get all static images. For example, debug toolbar will not have css, images or +# other assets shown. +python manage.py collectstatic --noinput + # touch-reload is added for development convenience, though it's not hooked up # to any automated watcher at the moment. uwsgi --http-socket 0.0.0.0:3031 --module agagd.wsgi \