diff --git a/browser.py b/browser.py new file mode 100644 index 0000000..e69de29 diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 0000000..9e6f44a --- /dev/null +++ b/core/utils.py @@ -0,0 +1,28 @@ +from django.http import HttpResponseRedirect +from django.shortcuts import redirect +from django.urls.exceptions import NoReverseMatch + + +def redirect_next(request, default): + """Redirects to a given page, except if the session has a next value given. + + Args: + request: WSGIRequest object + default: Default location to go to if no next is given. + + Returns: + HttpResponseRedirect + """ + if request: + if request.session.get("next"): + default = request.session["next"] + del request.session["next"] + elif request.GET.get("next"): + default = request.GET.get("next") + elif request.POST.get("next"): + default = request.POST.get("next") + + try: + return redirect(default) + except NoReverseMatch: + return HttpResponseRedirect(default) diff --git a/events/__init__.py b/events/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/events/admin.py b/events/admin.py new file mode 100644 index 0000000..bff2531 --- /dev/null +++ b/events/admin.py @@ -0,0 +1,93 @@ +from django.contrib import admin + +from events.models import Association, Exhibition, Rink, Tournament + +# from daterange_filter.filter import DateRangeFilter + + +@admin.register(Association) +class AssociationAdmin(admin.ModelAdmin): + list_display = [ + "name", + "full_name", + "location", + "website", + "exhibition_is_in_ohf", + "inserted", + ] + ordering = ["weight"] + + +@admin.register(Tournament) +class TournamentAdmin(admin.ModelAdmin): + # list_filter = ['association', 'verified', 'source', ('start_date', DateRangeFilter)] + list_filter = ["association", "verified", "source"] + list_display = ( + "association", + "sanction_number", + "name", + "location", + "start_date", + "verified", + "inserted_by", + "permits_count", + ) + search_fields = ["sanction_number", "start_date", "divisions"] + ordering = [ + "verified", + "start_date", + ] + + raw_id_fields = ["inserted_by"] + + fieldsets = ( + ( + None, + { + "fields": ( + "association", + "name", + "sanction_number", + "location", + ("start_date", "end_date"), + ) + }, + ), + ("Optional Data", {"fields": ("divisions",)}), + ( + "Advanced options", + { + "fields": ( + "source", + "verified", + "verified_date", + "inserted_by", + "website", + "notes", + ) + }, + ), + ) + + def permits_count(self, obj): + return obj.travelpermit_set.count() + + permits_count.short_description = "Permits" + + +@admin.register(Exhibition) +class ExhibitionAdmin(admin.ModelAdmin): + list_display = [ + "other_team", + "other_team_association", + "destination", + "arena", + "start_date", + ] + + raw_id_fields = ["inserted_by"] + + +@admin.register(Rink) +class RinkAdmin(admin.ModelAdmin): + pass diff --git a/events/apps.py b/events/apps.py new file mode 100644 index 0000000..15e10fb --- /dev/null +++ b/events/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class EventsConfig(AppConfig): + name = "events" + + # def ready(self): + # import events.signals diff --git a/events/association_profiles/GTHL.py b/events/association_profiles/GTHL.py new file mode 100644 index 0000000..7883141 --- /dev/null +++ b/events/association_profiles/GTHL.py @@ -0,0 +1,78 @@ +import datetime +import logging + +from bs4 import BeautifulSoup +from selenium.common.exceptions import WebDriverException + +log = logging.getLogger("events.commands.get_tournament_listing") + +ENABLED = True + + +def scan(browser, association): + + try: + browser.wait_until(element_name="btnList") + except AttributeError: + log.exception( + f'Unable to find submit button with name="btnList", skipping {association}' + ) + return None + except WebDriverException: + try: + browser.save_screenshot(sub_folder="WebDriverException") + except: + log.exception("Screenshot Failure") + return None + + browser.get_element(element_name="btnList").click() + + soup = BeautifulSoup(browser.browser.page_source, "html.parser") + + table = soup.find("table", {"class": "tblBorder"}) + table_body = table.find("tbody") + + columns = [ + "Sanction Number", + "Tournament Name", + "Centre", + "Start Date", + "End Date", + "Divisions", + "Details", + ] + rows = [] + + for row_index, row in enumerate(table_body.find_all("tr")): + row_data = {} + + cols = [ele.text.strip() for ele in row.find_all("td")] + + if not row_index: + continue + + for index, col in enumerate(columns): + row_data[col] = cols[index] + + try: + row_data["Start Date"] = datetime.datetime.strptime( + row_data["Start Date"], "%d-%b-%Y" + ).date() + except ValueError: + log.exception( + "{}: bad Start/End dates, expecting dd-Mth-YYYY, received {} and {}".format( + association.name, row_data["Start Date"], row_data["End Date"] + ) + ) + continue + + try: + row_data["End Date"] = datetime.datetime.strptime( + row_data["End Date"], "%d-%b-%Y" + ).date() + except ValueError: + row_data["End Date"] = row_data["Start Date"] + + rows.append(row_data) + + return rows diff --git a/events/association_profiles/HEO.py b/events/association_profiles/HEO.py new file mode 100644 index 0000000..8420ef6 --- /dev/null +++ b/events/association_profiles/HEO.py @@ -0,0 +1,120 @@ +""" + I've left this script unfinished. HEO does not publish the sanction number + on the main search table, you must visit each events page to get that number. + I do not want to be hitting their site once a week and then hitting 100+ event + pages to check and see if we already have the sanction number in our system. + I let Nathan know HEO is not possible for the time being. + + Why not match on title? What happens if the same title was used last year? + Why not match on date and title? What happens when either of those values + change, even in the slightest? + + It scans the first page (i did not add the system to follow to the next page) and + finds the events sanction number on the events page. +""" +import datetime +import logging +import traceback + +from bs4 import BeautifulSoup + +from browser import SKBrowserBase + +# from selenium.common.exceptions import WebDriverException + +log = logging.getLogger("events.commands.get_tournament_listing") + +ENABLED = False + + +def scan(browser: SKBrowserBase, association): + + raise ValueError("HEO is disabled at this time.") + + # try: + # browser.wait_until(element_id='edit-submit-tournament-listing') + # except AttributeError: + # log.debug(f'Unable to find Apply button with id="edit-submit-tournament-listing", skipping site: {association.name} {association.tournament_listing_url}') + # return None + # except WebDriverException: + # try: + # browser.save_screenshot(sub_folder='WebDriverException') + # except: + # log.debug(traceback.format_exc()) + # return None + + # with open('cache/heo-listing.html', 'w') as fw: + # fw.write(browser.browser.page_source) + with open("cache/heo-listing.html", "r") as fo: + source = fo.read() + + # soup = BeautifulSoup(browser.browser.page_source, "html.parser") + soup = BeautifulSoup(source, "html.parser") + + table = soup.find("table", {"class": "views-table"}) + table_body = table.find("tbody") + + columns = ["Date", "Tournament Name", "HLComp", "BodyChk", "Divisions"] + + rows = [] + + for row_index, row in enumerate(table_body.find_all("tr")): + row_data = {} + + cols = [ele.text.strip() for ele in row.find_all("td")] + + if not row_index: + continue + + for index, col in enumerate(columns): + row_data[col] = cols[index] + + link_to_event = row.find("td", {"class": "views-field-title"}).find( + "a", href=True + )["href"] + link_to_event = f"http://www.heominor.ca{link_to_event}" + + try: + start_raw, end_raw = row_data["Date"].split(" to ", 1) + + row_data["Start Date"] = datetime.datetime.strptime( + start_raw, "%b %d %Y" + ).date() + + try: + row_data["End Date"] = datetime.datetime.strptime( + end_raw, "%b %d %Y" + ).date() + except ValueError: + row_data["End Date"] = row_data["Start Date"] + + except ValueError: + log.debug(traceback.format_exc()) + log.debug( + "{}: bad Start/End dates, expecting dd-Mth-YYYY, received {} and {}".format( + association.name, row_data["Start Date"], row_data["End Date"] + ) + ) + continue + + print(row) + print(row_data) + + browser.load_url(link_to_event) + with open("cache/heo-listing-event.html", "w") as fw: + fw.write(browser.browser.page_source) + # with open('cache/heo-listing-event.html', 'r') as fo: + # source = fo.read() + + soup = BeautifulSoup(browser.browser.page_source, "html.parser") + # soup = BeautifulSoup(source, 'html.parser') + + sanction_number = ( + soup.find("div", {"class": "field-name-field-sanction-number"}) + .find("div", {"class": "field-items"}) + .text + ) + + row_data["Sanction Number"] = sanction_number + + return diff --git a/events/association_profiles/NOHA.py b/events/association_profiles/NOHA.py new file mode 100644 index 0000000..623078b --- /dev/null +++ b/events/association_profiles/NOHA.py @@ -0,0 +1,81 @@ +import datetime +import logging + +from bs4 import BeautifulSoup +from selenium.common.exceptions import WebDriverException + +log = logging.getLogger("events.commands.get_tournament_listing") + +ENABLED = True + + +def scan(browser, association): + + try: + browser.wait_until(element_name="btnList") + except AttributeError: + log.exception( + f'Unable to find submit button with name="btnList", skipping {association}' + ) + return None + except WebDriverException: + try: + browser.save_screenshot(sub_folder="WebDriverException") + except: + log.exception("Screenshot Failure") + return None + + browser.get_element(element_name="btnList").click() + + soup = BeautifulSoup(browser.browser.page_source, "html.parser") + + table = soup.find("table", {"class": "tblBorder"}) + table_body = table.find("tbody") + + columns = [ + "Sanction Number", + "Centre", + "Start Date", + "End Date", + "1", + "2", + "Divisions", + "Category", + ] + rows = [] + + for row_index, row in enumerate(table_body.find_all("tr")): + row_data = {} + + cols = [ele.text.strip() for ele in row.find_all("td")] + + if not row_index: + continue + + for index, col in enumerate(columns): + row_data[col] = cols[index] + + row_data["Tournament Name"] = row_data["Centre"] + + try: + row_data["Start Date"] = datetime.datetime.strptime( + row_data["Start Date"], "%d-%b-%Y" + ).date() + except ValueError: + log.exception( + "{}: bad Start/End dates, expecting dd-Mth-YYYY, received {} and {}".format( + association.name, row_data["Start Date"], row_data["End Date"] + ) + ) + continue + + try: + row_data["End Date"] = datetime.datetime.strptime( + row_data["End Date"], "%d-%b-%Y" + ).date() + except ValueError: + row_data["End Date"] = row_data["Start Date"] + + rows.append(row_data) + + return rows diff --git a/events/association_profiles/OMHA.py b/events/association_profiles/OMHA.py new file mode 100644 index 0000000..0d1a0d4 --- /dev/null +++ b/events/association_profiles/OMHA.py @@ -0,0 +1,81 @@ +import datetime +import logging +import time + +from bs4 import BeautifulSoup +from selenium.common.exceptions import WebDriverException + +log = logging.getLogger("events.commands.get_tournament_listing") + +ENABLED = True + + +def scan(browser, association): + + try: + browser.wait_until(element_name="btnList") + except AttributeError: + log.exception( + f'Unable to find submit button with name="btnList", skipping {association}' + ) + return None + except WebDriverException: + try: + browser.save_screenshot(sub_folder="WebDriverException") + except: + log.exception("Screenshot Failure") + return None + + browser.get_element(element_name="btnList").click() + + time.sleep(2) + + soup = BeautifulSoup(browser.browser.page_source, "html.parser") + + table = soup.find("table", {"class": "tbl-tournament"}) + table_body = table.find("tbody") + + columns = [ + "Sanction Number", + "Start Date", + "End Date", + "Tournament Name", + "Centre", + "Divisions", + "Details", + ] + rows = [] + + for row_index, row in enumerate(table_body.find_all("tr")): + row_data = {} + + cols = [ele.text.strip() for ele in row.find_all("td")] + + if not row_index: + continue + + for index, col in enumerate(columns): + row_data[col] = cols[index] + + try: + row_data["Start Date"] = datetime.datetime.strptime( + row_data["Start Date"], "%d-%b-%Y" + ).date() + except ValueError: + log.exception( + "{}: bad Start/End dates, expecting dd-Mth-YYYY, received {} and {}".format( + association.name, row_data["Start Date"], row_data["End Date"] + ) + ) + continue + + try: + row_data["End Date"] = datetime.datetime.strptime( + row_data["End Date"], "%d-%b-%Y" + ).date() + except ValueError: + row_data["End Date"] = row_data["Start Date"] + + rows.append(row_data) + + return rows diff --git a/events/association_profiles/__init__.py b/events/association_profiles/__init__.py new file mode 100644 index 0000000..05b57fb --- /dev/null +++ b/events/association_profiles/__init__.py @@ -0,0 +1 @@ +from . import GTHL, HEO, NOHA, OMHA diff --git a/events/forms.py b/events/forms.py new file mode 100644 index 0000000..cff721b --- /dev/null +++ b/events/forms.py @@ -0,0 +1,150 @@ +from django import forms +from django.utils import timezone + +from events.models import Exhibition, Tournament + + +class NewTournamentForm(forms.ModelForm): + required_css_class = "required" + + class Meta: + model = Tournament + widgets = { + "name": forms.TextInput(attrs={"class": "form-control"}), + "sanction_number": forms.TextInput(attrs={"class": "form-control"}), + "location": forms.TextInput( + attrs={"class": "form-control", "placeholder": "City, Province/State"} + ), + "start_date": forms.DateInput( + attrs={"class": "datepicker form-control", "placeholder": "YYYY-MM-DD"} + ), + "end_date": forms.DateInput( + attrs={"class": "datepicker form-control", "placeholder": "YYYY-MM-DD"} + ), + "association": forms.Select(attrs={"class": "form-control"}), + "website": forms.TextInput( + attrs={ + "placeholder": "URL to webpage about tournament. Optional but recommended to supply" + } + ), + } + fields = ( + "name", + "association", + "association_other", + "sanction_number", + "location", + "start_date", + "end_date", + "website", + ) + + def clean(self): + cleaned_data = super().clean() + + start_date = cleaned_data.get("start_date") + end_date = cleaned_data.get("end_date") + + if start_date and end_date and start_date > end_date: + self.add_error("end_date", "End date cannot be before start date") + + return cleaned_data + + +class NewExhibitionForm(forms.ModelForm): + required_css_class = "required" + + class Meta: + model = Exhibition + widgets = { + "destination": forms.TextInput(attrs={"placeholder": "City, Province"}), + } + + fields = ( + "other_team", + "other_team_association", + "other_team_association_other", + "destination", + "arena", + "start_date", + "end_datetime", + "rink", + "referee_requirements", + "timekeeper_needed", + "timekeeper_notes", + "required_referee_or_timekeeper", + "cell_phone", + "contact_name", + ) + + start_date = forms.DateTimeField( + input_formats=["%Y-%m-%d %I:%M %p", "%Y-%m-%d %H:%M:%S"], + widget=forms.DateTimeInput( + attrs={"class": "datepicker", "placeholder": "YYYY-MM-DD HH:MM AM/PM"} + ), + ) + + end_datetime = forms.DateTimeField( + input_formats=["%Y-%m-%d %I:%M %p", "%Y-%m-%d %H:%M:%S"], + widget=forms.DateTimeInput( + attrs={"class": "datepicker", "placeholder": "YYYY-MM-DD HH:MM AM/PM"} + ), + ) + + req_ack = forms.BooleanField(required=False) + + def clean_end_datetime(self): + try: + start = self.cleaned_data["start_date"] + end = self.cleaned_data["end_datetime"] + return timezone.make_aware( + timezone.datetime( + year=start.year, + month=start.month, + day=start.day, + hour=end.hour, + minute=end.minute, + second=end.second, + ) + ) + except: + raise forms.ValidationError("System Error, try again or contact the office") + + def clean(self): + + cleaned_data = super(NewExhibitionForm, self).clean() + + try: + start = self.cleaned_data["start_date"] + end = self.cleaned_data["end_datetime"] + + diff = end - start + diff = divmod(diff.days * 86400 + diff.seconds, 60)[0] + + if diff <= 30: + self.add_error( + "end_datetime", + f"Exhibition is too short at {diff} minutes, " + f"it must be longer than 30 minutes", + ) + except: + pass + + if cleaned_data.get("required_referee_or_timekeeper"): + + if not cleaned_data.get("referee_requirements"): + self.add_error( + "referee_requirements", + "Referee Requirements required if requesting officials.", + ) + + if not cleaned_data.get("req_ack"): + self.add_error("req_ack", "You must accept this acknowledgement") + + if not cleaned_data.get("cell_phone"): + self.add_error( + "cell_phone", + "A Cell Phone number is required in case something comes up", + ) + + return cleaned_data diff --git a/events/management/__init__.py b/events/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/events/management/commands/__init__.py b/events/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/events/management/commands/get_tournament_listing.py b/events/management/commands/get_tournament_listing.py new file mode 100644 index 0000000..774c303 --- /dev/null +++ b/events/management/commands/get_tournament_listing.py @@ -0,0 +1,123 @@ +import logging + +from django.core.management.base import BaseCommand +from django.utils import timezone +from django_templated_emailer.models import EmailQueue + +import events.association_profiles +from browser import SKBrowserBase +from events.models import Association, Tournament +from project_settings import proj_settings + +log = logging.getLogger("events.commands.get_tournament_listing") + + +class Command(BaseCommand): + help = "Loads tournaments from Associations that have a tournament listing page" + + def handle(self, *args, **options): + browser = None + + # Tournament.objects.filter(created__date=timezone.now().date()).delete() + + tournaments_with_incorrect_dates = [] + + for association in Association.objects.exclude(tournament_listing_url=""): + + profile = getattr(events.association_profiles, association.name, None) + if not profile: + self.stdout.write(f"Scanner profile for {association} not found.") + continue + elif not profile.ENABLED: + self.stdout.write(f"Scanner profile disabled for {association}") + continue + + if not browser: + browser = SKBrowserBase() + + self.stdout.write(f"{association} {association.tournament_listing_url}") + + browser.load_url(association.tournament_listing_url) + + try: + rows = profile.scan(browser, association) + except: + log.exception(f"Scan failure on {association}") + continue + + if rows is None: + self.stdout.write( + f"See log for details on {association} as something went wrong." + ) + continue + + self.stdout.write(f"{association} found {len(rows)} tournaments.") + + for row_data in rows: + + # If the End Date comes BEFORE the Start Date, check if the Day and + # Month between both dates are the same. If they match, its more than + # likely just an invalid year value. + # 2019-10-30: Disabled for the time being, too many incorrect dates coming from OMHA. + # if row_data['Start Date'] > row_data['End Date']: + # print(row_data) + # + # if row_data['Start Date'].day == row_data['End Date'].day and \ + # row_data['Start Date'].month == row_data['End Date'].month: + # row_data['End Date'] = row_data['End Date'].replace(year=row_data['Start Date'].year) + # + # print(row_data) + + try: + defaults = { + "name": row_data["Tournament Name"], + "location": row_data["Centre"], + "divisions": row_data["Divisions"], + "start_date": row_data["Start Date"], + "end_date": row_data["End Date"], + "verified": True, + "verified_date": timezone.now(), + "source": "tournament_listing_url", + "website": association.tournament_listing_url, + "notes": "Added by get_tournament_listing command", + } + + t, was_created = Tournament.objects.get_or_create( + association=association, + sanction_number=row_data["Sanction Number"], + defaults=defaults, + ) + + # No longer saving updates due to us having to manually edit End Date's + # if not was_created: + # core_data.base_functions.only_save_changed( + # obj=t, + # defaults=defaults, + # not_these_fields=['notes', 'verified_date'] + # ) + + # If the End date comes BEFORE the start date, email someone about it. + if was_created and t.start_date > t.end_date: + tournaments_with_incorrect_dates.append(t) + + except Tournament.MultipleObjectsReturned: + log.warning( + f"{association} returned multiple records for a Tournament sanction {row_data['Sanction Number']}" + ) + + except KeyError: + log.debug(row_data) + log.exception( + f"Stopped processing association {association} as there is something wrong." + ) + break + + if tournaments_with_incorrect_dates: + EmailQueue.queue_email( + template_name="System - Event Scanner - Invalid Dates Found", + domain=proj_settings.DOMAIN_BASE, + tournaments=tournaments_with_incorrect_dates, + ) + + if browser: + browser.quit() diff --git a/events/migrations/0001_initial.py b/events/migrations/0001_initial.py new file mode 100644 index 0000000..27f50ee --- /dev/null +++ b/events/migrations/0001_initial.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-06-28 18:28 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Association", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="Association")), + ("full_name", models.CharField(max_length=255)), + ("location", models.CharField(blank=True, max_length=255)), + ("website", models.CharField(blank=True, max_length=500)), + ( + "tournament_listing_url", + models.CharField(blank=True, max_length=500), + ), + ("weight", models.IntegerField(default=100)), + ("created", models.DateTimeField(auto_now_add=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="Exhibition", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("season_id", models.IntegerField(default=0)), + ("name", models.CharField(blank=True, max_length=255)), + ("other_team", models.CharField(max_length=255)), + ( + "destination", + models.CharField(help_text="City, Province", max_length=255), + ), + ("arena", models.CharField(blank=True, max_length=255)), + ("start_date", models.DateField()), + ("end_date", models.DateField(blank=True, null=True)), + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ("source", models.CharField(blank=True, max_length=255)), + ("notes", models.TextField(blank=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="Tournament", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("season_id", models.IntegerField(default=0)), + ( + "association_other", + models.CharField( + blank=True, + help_text="Only supply if the association is not listed above.", + max_length=255, + ), + ), + ("sanction_number", models.CharField(blank=True, max_length=255)), + ("name", models.CharField(max_length=255)), + ( + "location", + models.CharField(help_text="City, Province", max_length=255), + ), + ( + "start_date", + models.DateField( + help_text="Please supply the start date of the tournament itself, not just the date you play if the two dates differ." + ), + ), + ("end_date", models.DateField(blank=True, null=True)), + ( + "divisions", + models.CharField( + blank=True, + help_text="Divisions tournament is for", + max_length=4000, + ), + ), + ("source", models.CharField(default="email", max_length=255)), + ("verified", models.BooleanField(default=False)), + ("verified_date", models.DateField(blank=True, null=True)), + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ( + "website", + models.CharField( + blank=True, + help_text="URL to webpage about tournament", + max_length=4000, + ), + ), + ("notes", models.TextField(blank=True)), + ( + "association", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="events.Association", + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "permissions": (("tournament_verify", "Can verify legitimacy."),), + }, + ), + ] diff --git a/events/migrations/0002_auto_20170629_0946.py b/events/migrations/0002_auto_20170629_0946.py new file mode 100644 index 0000000..26d19b8 --- /dev/null +++ b/events/migrations/0002_auto_20170629_0946.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-06-29 13:46 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0001_initial"), + ] + + operations = [ + migrations.RemoveField( + model_name="exhibition", + name="season_id", + ), + migrations.RemoveField( + model_name="tournament", + name="season_id", + ), + ] diff --git a/events/migrations/0003_auto_20170630_1502.py b/events/migrations/0003_auto_20170630_1502.py new file mode 100644 index 0000000..1ca5165 --- /dev/null +++ b/events/migrations/0003_auto_20170630_1502.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-06-30 19:02 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0002_auto_20170629_0946"), + ] + + operations = [ + migrations.AlterField( + model_name="tournament", + name="source", + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/events/migrations/0004_auto_20170706_0851.py b/events/migrations/0004_auto_20170706_0851.py new file mode 100644 index 0000000..dbdd8e6 --- /dev/null +++ b/events/migrations/0004_auto_20170706_0851.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-07-06 12:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0003_auto_20170630_1502"), + ] + + operations = [ + migrations.AlterField( + model_name="tournament", + name="association_other", + field=models.CharField( + blank=True, + help_text='Only supply if the association is "Other".', + max_length=255, + ), + ), + migrations.AlterField( + model_name="tournament", + name="divisions", + field=models.CharField( + blank=True, + help_text="Divisions tournament is for. * Optional", + max_length=4000, + ), + ), + migrations.AlterField( + model_name="tournament", + name="start_date", + field=models.DateField( + help_text="Please supply the start date of the tournament" + ), + ), + migrations.AlterField( + model_name="tournament", + name="website", + field=models.CharField( + blank=True, + help_text="URL to webpage about tournament. * Optional but recommended to supply", + max_length=4000, + ), + ), + ] diff --git a/events/migrations/0005_auto_20170828_0957.py b/events/migrations/0005_auto_20170828_0957.py new file mode 100644 index 0000000..fce623e --- /dev/null +++ b/events/migrations/0005_auto_20170828_0957.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-08-28 13:57 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0004_auto_20170706_0851"), + ] + + operations = [ + migrations.RemoveField( + model_name="exhibition", + name="end_date", + ), + migrations.AddField( + model_name="exhibition", + name="other_team_association", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="events.Association", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="exhibition", + name="other_team_association_other", + field=models.CharField( + blank=True, + help_text='Only supply if the association is "Other".', + max_length=255, + ), + ), + migrations.AlterField( + model_name="exhibition", + name="start_date", + field=models.DateTimeField(), + ), + ] diff --git a/events/migrations/0006_auto_20170828_1011.py b/events/migrations/0006_auto_20170828_1011.py new file mode 100644 index 0000000..71be813 --- /dev/null +++ b/events/migrations/0006_auto_20170828_1011.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-08-28 14:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0005_auto_20170828_0957"), + ] + + operations = [ + migrations.AlterField( + model_name="exhibition", + name="other_team_association", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="events.Association", + verbose_name="Other Team Association", + ), + ), + migrations.AlterField( + model_name="exhibition", + name="other_team_association_other", + field=models.CharField( + blank=True, + help_text='If you have selected "Other" for the Other Team Association, who is it?', + max_length=255, + ), + ), + migrations.AlterField( + model_name="exhibition", + name="start_date", + field=models.DateTimeField(verbose_name="Start Date and Time"), + ), + migrations.AlterField( + model_name="tournament", + name="association", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="events.Association", + verbose_name="Host Association", + ), + ), + migrations.AlterField( + model_name="tournament", + name="association_other", + field=models.CharField( + blank=True, + help_text='If you have selected "Other" for the Host Association, who is it?', + max_length=255, + verbose_name="Host Association Other", + ), + ), + ] diff --git a/events/migrations/0007_auto_20170907_1709.py b/events/migrations/0007_auto_20170907_1709.py new file mode 100644 index 0000000..f5408c1 --- /dev/null +++ b/events/migrations/0007_auto_20170907_1709.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-07 21:09 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0006_auto_20170828_1011"), + ] + + operations = [ + migrations.CreateModel( + name="Rink", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("city", models.CharField(max_length=255)), + ("province", models.CharField(max_length=255)), + ], + ), + migrations.AddField( + model_name="exhibition", + name="end_date", + field=models.DateTimeField( + blank=True, null=True, verbose_name="End Date and Time" + ), + ), + migrations.AddField( + model_name="exhibition", + name="referee_requirements", + field=models.CharField( + choices=[ + ("2 Refs", "2 Refs"), + ("1 Ref / 2 Linesmen", "1 Ref / 2 Linesmen"), + ("2 Refs / 2 Linesmen", "2 Refs / 2 Linesmen"), + ], + default="No Referee Needed", + max_length=255, + ), + ), + migrations.AlterField( + model_name="exhibition", + name="arena", + field=models.CharField( + blank=True, + help_text="Ensure you include the pad/rink number as well!", + max_length=255, + ), + ), + migrations.AddField( + model_name="exhibition", + name="rink", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="events.Rink", + ), + ), + ] diff --git a/events/migrations/0008_auto_20170907_1933.py b/events/migrations/0008_auto_20170907_1933.py new file mode 100644 index 0000000..ff13885 --- /dev/null +++ b/events/migrations/0008_auto_20170907_1933.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-07 23:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0007_auto_20170907_1709"), + ] + + operations = [ + migrations.AddField( + model_name="exhibition", + name="requires_referee", + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name="exhibition", + name="referee_requirements", + field=models.CharField( + blank=True, + choices=[ + ("No Referee Needed *", "No Referee Needed *"), + ("2 Refs", "2 Refs"), + ("1 Ref / 2 Linesmen", "1 Ref / 2 Linesmen"), + ("2 Refs / 2 Linesmen", "2 Refs / 2 Linesmen"), + ], + default="No Referee Needed *", + help_text="* Timekeeper must be knowledgeable on how to properly fill out a game sheet", + max_length=255, + ), + ), + ] diff --git a/events/migrations/0009_auto_20170907_1955.py b/events/migrations/0009_auto_20170907_1955.py new file mode 100644 index 0000000..7ab0021 --- /dev/null +++ b/events/migrations/0009_auto_20170907_1955.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-07 23:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0008_auto_20170907_1933"), + ] + + operations = [ + migrations.RemoveField( + model_name="exhibition", + name="requires_referee", + ), + migrations.AddField( + model_name="exhibition", + name="timekeeper_needed", + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name="exhibition", + name="referee_requirements", + field=models.CharField( + blank=True, + choices=[ + ("2 Refs", "2 Refs"), + ("1 Ref / 2 Linesmen", "1 Ref / 2 Linesmen"), + ("2 Refs / 2 Linesmen", "2 Refs / 2 Linesmen"), + ], + help_text="* Timekeeper must be knowledgeable on how to properly fill out a game sheet", + max_length=255, + ), + ), + ] diff --git a/events/migrations/0010_auto_20170907_2000.py b/events/migrations/0010_auto_20170907_2000.py new file mode 100644 index 0000000..72837a8 --- /dev/null +++ b/events/migrations/0010_auto_20170907_2000.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-08 00:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0009_auto_20170907_1955"), + ] + + operations = [ + migrations.AddField( + model_name="exhibition", + name="timekeeper_notes", + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name="exhibition", + name="referee_requirements", + field=models.CharField( + blank=True, + choices=[ + ("2 Refs", "2 Refs"), + ("1 Ref / 2 Linesmen", "1 Ref / 2 Linesmen"), + ("2 Refs / 2 Linesmen", "2 Refs / 2 Linesmen"), + ], + max_length=255, + ), + ), + ] diff --git a/events/migrations/0011_exhibition_required_referee_or_timekeeper.py b/events/migrations/0011_exhibition_required_referee_or_timekeeper.py new file mode 100644 index 0000000..9168b41 --- /dev/null +++ b/events/migrations/0011_exhibition_required_referee_or_timekeeper.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-08 00:13 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0010_auto_20170907_2000"), + ] + + operations = [ + migrations.AddField( + model_name="exhibition", + name="required_referee_or_timekeeper", + field=models.BooleanField(default=False), + ), + ] diff --git a/events/migrations/0012_auto_20170907_2015.py b/events/migrations/0012_auto_20170907_2015.py new file mode 100644 index 0000000..1369419 --- /dev/null +++ b/events/migrations/0012_auto_20170907_2015.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-08 00:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0011_exhibition_required_referee_or_timekeeper"), + ] + + operations = [ + migrations.AlterField( + model_name="exhibition", + name="arena", + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/events/migrations/0013_auto_20170908_1247.py b/events/migrations/0013_auto_20170908_1247.py new file mode 100644 index 0000000..b051881 --- /dev/null +++ b/events/migrations/0013_auto_20170908_1247.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-08 16:47 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0012_auto_20170907_2015"), + ] + + operations = [ + migrations.AlterField( + model_name="exhibition", + name="destination", + field=models.CharField(max_length=255), + ), + migrations.AlterField( + model_name="exhibition", + name="other_team_association_other", + field=models.CharField( + blank=True, + max_length=255, + verbose_name='Other Team Association "Other"', + ), + ), + migrations.AlterField( + model_name="tournament", + name="association_other", + field=models.CharField( + blank=True, max_length=255, verbose_name='Host Association "Other"' + ), + ), + migrations.AlterField( + model_name="tournament", + name="location", + field=models.CharField(max_length=255), + ), + migrations.AlterField( + model_name="tournament", + name="start_date", + field=models.DateField(verbose_name="Tournament Start Date"), + ), + migrations.AlterField( + model_name="tournament", + name="website", + field=models.CharField(blank=True, max_length=4000), + ), + ] diff --git a/events/migrations/0014_auto_20170908_1534.py b/events/migrations/0014_auto_20170908_1534.py new file mode 100644 index 0000000..689cf38 --- /dev/null +++ b/events/migrations/0014_auto_20170908_1534.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-08 19:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0013_auto_20170908_1247"), + ] + + operations = [ + migrations.AlterModelOptions( + name="rink", + options={"ordering": ["name"]}, + ), + migrations.AddField( + model_name="exhibition", + name="referee_timekeeper_request_sent_to_office", + field=models.BooleanField(default=False), + ), + ] diff --git a/events/migrations/0015_remove_exhibition_end_date.py b/events/migrations/0015_remove_exhibition_end_date.py new file mode 100644 index 0000000..393ebf5 --- /dev/null +++ b/events/migrations/0015_remove_exhibition_end_date.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-12 18:25 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0014_auto_20170908_1534"), + ] + + operations = [ + migrations.RemoveField( + model_name="exhibition", + name="end_date", + ), + ] diff --git a/events/migrations/0016_auto_20171013_1705.py b/events/migrations/0016_auto_20171013_1705.py new file mode 100644 index 0000000..8a1d0f2 --- /dev/null +++ b/events/migrations/0016_auto_20171013_1705.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-13 21:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0015_remove_exhibition_end_date"), + ] + + operations = [ + migrations.AddField( + model_name="exhibition", + name="cell_phone", + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name="exhibition", + name="end_datetime", + field=models.DateTimeField( + blank=True, null=True, verbose_name="End Date and Time" + ), + ), + ] diff --git a/events/migrations/0017_association_exhibition_is_in_ohf.py b/events/migrations/0017_association_exhibition_is_in_ohf.py new file mode 100644 index 0000000..4ccd433 --- /dev/null +++ b/events/migrations/0017_association_exhibition_is_in_ohf.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-31 19:47 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0016_auto_20171013_1705"), + ] + + operations = [ + migrations.AddField( + model_name="association", + name="exhibition_is_in_ohf", + field=models.BooleanField(default=False), + ), + ] diff --git a/events/migrations/0018_auto_20171107_1848.py b/events/migrations/0018_auto_20171107_1848.py new file mode 100644 index 0000000..6585640 --- /dev/null +++ b/events/migrations/0018_auto_20171107_1848.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-07 23:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0017_association_exhibition_is_in_ohf"), + ] + + operations = [ + migrations.AlterField( + model_name="association", + name="exhibition_is_in_ohf", + field=models.BooleanField( + default=False, + help_text="Setting applies to Exhibitions only, is this association within the OHF?", + ), + ), + ] diff --git a/events/migrations/0019_auto_20171122_1635.py b/events/migrations/0019_auto_20171122_1635.py new file mode 100644 index 0000000..49870d5 --- /dev/null +++ b/events/migrations/0019_auto_20171122_1635.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-22 21:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0018_auto_20171107_1848"), + ] + + operations = [ + migrations.AlterField( + model_name="rink", + name="city", + field=models.CharField(blank=True, max_length=255), + ), + migrations.AlterField( + model_name="rink", + name="province", + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/events/migrations/0020_rink_weight.py b/events/migrations/0020_rink_weight.py new file mode 100644 index 0000000..400bd1c --- /dev/null +++ b/events/migrations/0020_rink_weight.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-22 21:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0019_auto_20171122_1635"), + ] + + operations = [ + migrations.AddField( + model_name="rink", + name="weight", + field=models.PositiveIntegerField(default=100), + ), + ] diff --git a/events/migrations/0021_auto_20171122_1647.py b/events/migrations/0021_auto_20171122_1647.py new file mode 100644 index 0000000..f3bd2d2 --- /dev/null +++ b/events/migrations/0021_auto_20171122_1647.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-22 21:47 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0020_rink_weight"), + ] + + operations = [ + migrations.AlterModelOptions( + name="rink", + options={"ordering": ["weight", "name"]}, + ), + ] diff --git a/events/migrations/0022_auto_20171122_1652.py b/events/migrations/0022_auto_20171122_1652.py new file mode 100644 index 0000000..49847ad --- /dev/null +++ b/events/migrations/0022_auto_20171122_1652.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-22 21:52 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0021_auto_20171122_1647"), + ] + + operations = [ + migrations.AlterField( + model_name="exhibition", + name="rink", + field=models.ForeignKey( + default=19, + on_delete=django.db.models.deletion.CASCADE, + to="events.Rink", + ), + preserve_default=False, + ), + ] diff --git a/events/migrations/0023_auto_20180907_1743.py b/events/migrations/0023_auto_20180907_1743.py new file mode 100644 index 0000000..90b3194 --- /dev/null +++ b/events/migrations/0023_auto_20180907_1743.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-09-07 21:43 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0022_auto_20171122_1652"), + ] + + operations = [ + migrations.AlterField( + model_name="exhibition", + name="cell_phone", + field=models.CharField( + blank=True, + help_text="This is the phone number you will be contacted at if there is a problem.", + max_length=255, + ), + ), + ] diff --git a/events/migrations/0024_auto_20181127_1821.py b/events/migrations/0024_auto_20181127_1821.py new file mode 100644 index 0000000..fec12e8 --- /dev/null +++ b/events/migrations/0024_auto_20181127_1821.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.3 on 2018-11-27 23:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0023_auto_20180907_1743"), + ] + + operations = [ + migrations.AlterField( + model_name="exhibition", + name="timekeeper_needed", + field=models.BooleanField(blank=True, default=False), + ), + ] diff --git a/events/migrations/0025_exhibition_contact_name.py b/events/migrations/0025_exhibition_contact_name.py new file mode 100644 index 0000000..825d2b9 --- /dev/null +++ b/events/migrations/0025_exhibition_contact_name.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.5 on 2019-04-10 19:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0024_auto_20181127_1821"), + ] + + operations = [ + migrations.AddField( + model_name="exhibition", + name="contact_name", + field=models.CharField( + blank=True, + help_text="Whose phone number are you supplying above?", + max_length=255, + ), + ), + ] diff --git a/events/migrations/0026_auto_20190531_1646.py b/events/migrations/0026_auto_20190531_1646.py new file mode 100644 index 0000000..f17f029 --- /dev/null +++ b/events/migrations/0026_auto_20190531_1646.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.5 on 2019-05-31 20:46 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0025_exhibition_contact_name"), + ] + + operations = [ + migrations.RenameField( + model_name="association", + old_name="created", + new_name="inserted", + ), + migrations.RemoveField( + model_name="association", + name="created_by", + ), + ] diff --git a/events/migrations/0027_remove_exhibition_created_and_more.py b/events/migrations/0027_remove_exhibition_created_and_more.py new file mode 100644 index 0000000..1bf7563 --- /dev/null +++ b/events/migrations/0027_remove_exhibition_created_and_more.py @@ -0,0 +1,204 @@ +# Generated by Django 4.0.3 on 2022-03-24 13:28 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("events", "0026_auto_20190531_1646"), + ] + + operations = [ + migrations.RemoveField( + model_name="exhibition", + name="created", + ), + migrations.RemoveField( + model_name="exhibition", + name="created_by", + ), + migrations.RemoveField( + model_name="exhibition", + name="last_updated", + ), + migrations.RemoveField( + model_name="tournament", + name="created", + ), + migrations.RemoveField( + model_name="tournament", + name="created_by", + ), + migrations.RemoveField( + model_name="tournament", + name="last_updated", + ), + migrations.AddField( + model_name="association", + name="inserted_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_inserted", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="association", + name="old_sk_id", + field=models.PositiveIntegerField( + blank=True, + help_text="The old primary id for the entry", + null=True, + unique=True, + ), + ), + migrations.AddField( + model_name="association", + name="updated", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name="association", + name="updated_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_updated", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="exhibition", + name="inserted", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name="exhibition", + name="inserted_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_inserted", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="exhibition", + name="old_sk_id", + field=models.PositiveIntegerField( + blank=True, + help_text="The old primary id for the entry", + null=True, + unique=True, + ), + ), + migrations.AddField( + model_name="exhibition", + name="updated", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name="exhibition", + name="updated_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_updated", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="rink", + name="inserted", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name="rink", + name="inserted_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_inserted", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="rink", + name="old_sk_id", + field=models.PositiveIntegerField( + blank=True, + help_text="The old primary id for the entry", + null=True, + unique=True, + ), + ), + migrations.AddField( + model_name="rink", + name="updated", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name="rink", + name="updated_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_updated", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="tournament", + name="inserted", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name="tournament", + name="inserted_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_inserted", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="tournament", + name="old_sk_id", + field=models.PositiveIntegerField( + blank=True, + help_text="The old primary id for the entry", + null=True, + unique=True, + ), + ), + migrations.AddField( + model_name="tournament", + name="updated", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name="tournament", + name="updated_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_updated", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/events/migrations/0028_alter_association_inserted_alter_association_weight_and_more.py b/events/migrations/0028_alter_association_inserted_alter_association_weight_and_more.py new file mode 100644 index 0000000..4c531f1 --- /dev/null +++ b/events/migrations/0028_alter_association_inserted_alter_association_weight_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.0.3 on 2022-03-24 13:49 + +from django.db import migrations, models +import django.db.models.functions.text +import django.utils.timezone +import positions.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0027_remove_exhibition_created_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="association", + name="inserted", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name="association", + name="weight", + field=positions.fields.PositionField(default=-1), + ), + migrations.AlterField( + model_name="rink", + name="weight", + field=positions.fields.PositionField(default=-1), + ), + migrations.AddConstraint( + model_name="association", + constraint=models.UniqueConstraint( + django.db.models.functions.text.Lower("name"), + name="association_name_uniqueness", + ), + ), + migrations.AddConstraint( + model_name="rink", + constraint=models.UniqueConstraint( + django.db.models.functions.text.Lower("name"), + django.db.models.functions.text.Lower("city"), + django.db.models.functions.text.Lower("province"), + name="rink_name_city_province_uniqueness", + ), + ), + ] diff --git a/events/migrations/__init__.py b/events/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/events/models.py b/events/models.py new file mode 100644 index 0000000..d200b24 --- /dev/null +++ b/events/models.py @@ -0,0 +1,192 @@ +import pytz +from django.conf import settings +from django.contrib.auth.models import User +from django.db import models +from django.db.models.functions import Lower +from django.utils import timezone +from positions.fields import PositionField + +from core.model_helpers import _BaseModel + +# from travelpermits.models import TravelPermit + + +class Association(_BaseModel): + class Meta: + constraints = [ + models.UniqueConstraint( + Lower("name"), + name="association_name_uniqueness", + ) + ] + + name = models.CharField(max_length=255, verbose_name="Association") + full_name = models.CharField(max_length=255) + location = models.CharField(max_length=255, blank=True) + website = models.CharField(max_length=500, blank=True) + tournament_listing_url = models.CharField(max_length=500, blank=True) + + exhibition_is_in_ohf = models.BooleanField( + default=False, + help_text="Setting applies to Exhibitions only, is this association within the OHF?", + ) + + weight = PositionField() + + def __str__(self): + if self.name.lower() == "other": + return "Other (Supply Below)" + return self.name + + def is_omha(self): + return self.name in ["OMHA"] + + +class Rink(_BaseModel): + class Meta: + ordering = ["weight", "name"] + constraints = [ + models.UniqueConstraint( + Lower("name"), + Lower("city"), + Lower("province"), + name="rink_name_city_province_uniqueness", + ) + ] + + name = models.CharField(max_length=255) + city = models.CharField(max_length=255, blank=True) + province = models.CharField(max_length=255, blank=True) + + weight = PositionField() + + def __str__(self): + return f"{self.name}, {self.city}, {self.province}" + + +class Tournament(_BaseModel): + class Meta: + permissions = (("tournament_verify", "Can verify legitimacy."),) + + association = models.ForeignKey( + Association, on_delete=models.CASCADE, verbose_name="Host Association" + ) + association_other = models.CharField( + max_length=255, blank=True, verbose_name='Host Association "Other"' + ) + + sanction_number = models.CharField(max_length=255, blank=True) + + name = models.CharField(max_length=255) + location = models.CharField(max_length=255) + start_date = models.DateField(verbose_name="Tournament Start Date") + end_date = models.DateField(blank=True, null=True) + divisions = models.CharField( + max_length=4000, blank=True, help_text="Divisions tournament is for. * Optional" + ) + + source = models.CharField(max_length=255, blank=True) + verified = models.BooleanField(default=False) + verified_date = models.DateField(blank=True, null=True) + + # Let's us store where they found the tournament information from if they are + # submitting a tournament we don't already know + website = models.CharField(max_length=4000, blank=True) + + notes = models.TextField(blank=True) + + def has_been_verified(self): + self.verified = True + self.verified_date = timezone.now() + self.save() + + def __str__(self): + return f'{self.association}: {self.start_date} to {self.end_date} - "{self.name}" at "{self.location}"' + + def is_in_omha_region(self): + return self.association.is_omha() + + def can_edit(self): + return not self.travelpermit_set.exclude(number=0).exists() + + +class Exhibition(_BaseModel): + + name = models.CharField(max_length=255, blank=True) + + other_team = models.CharField(max_length=255) + other_team_association = models.ForeignKey( + Association, on_delete=models.CASCADE, verbose_name="Other Team Association" + ) + other_team_association_other = models.CharField( + max_length=255, blank=True, verbose_name='Other Team Association "Other"' + ) + + destination = models.CharField(max_length=255) + arena = models.CharField(max_length=255, blank=True) + + start_date = models.DateTimeField(verbose_name="Start Date and Time") + end_datetime = models.DateTimeField( + null=True, blank=True, verbose_name="End Date and Time" + ) + + source = models.CharField(max_length=255, blank=True) + notes = models.TextField(blank=True) + + REF_REQ_TWO_REFS = "2 Refs" + REF_REQ_ONE_REF_TWO_LINESMEN = "1 Ref / 2 Linesmen" + REF_REQ_TWO_REF_TWO_LINESMEN = "2 Refs / 2 Linesmen" + + REF_REQUIREMENTS = ( + (REF_REQ_TWO_REFS, REF_REQ_TWO_REFS), + (REF_REQ_ONE_REF_TWO_LINESMEN, REF_REQ_ONE_REF_TWO_LINESMEN), + (REF_REQ_TWO_REF_TWO_LINESMEN, REF_REQ_TWO_REF_TWO_LINESMEN), + ) + + required_referee_or_timekeeper = models.BooleanField(default=False) + + rink = models.ForeignKey(Rink, on_delete=models.CASCADE) + referee_requirements = models.CharField( + max_length=255, choices=REF_REQUIREMENTS, blank=True + ) + timekeeper_needed = models.BooleanField(default=False, blank=True) + timekeeper_notes = models.TextField(blank=True) + + contact_name = models.CharField( + max_length=255, + blank=True, + help_text="Whose phone number are you supplying above?", + ) + cell_phone = models.CharField( + max_length=255, + blank=True, + help_text="This is the phone number you will be contacted at if there is a problem.", + ) + + referee_timekeeper_request_sent_to_office = models.BooleanField(default=False) + + def __str__(self): + return ", ".join( + [self.other_team, self.destination, self.start_date.strftime("%Y-%m-%d")] + ) + + def is_in_ohf_region(self): + return self.other_team_association.exhibition_is_in_ohf + + def get_start_date_local_timezone(self): + return self.start_date.astimezone(pytz.timezone(settings.TIME_ZONE)) + + def get_arena(self): + + if self.rink and self.rink.name.startswith("Other"): + return self.arena + + return "-".join([self.rink.name, self.destination]) + + def can_edit(self): + return not self.travelpermit_set.exclude(number=0).exists() + + def get_other_team_association(self): + if self.other_team_association.name == "OTHER": + return self.other_team_association_other + return self.other_team_association diff --git a/events/signals.py b/events/signals.py new file mode 100644 index 0000000..856ccd3 --- /dev/null +++ b/events/signals.py @@ -0,0 +1,16 @@ +from django.db.models.signals import post_save + +from events.models import Tournament + +# from travelpermits.models import TravelPermit + + +def tournament_verified_update_permits(instance: Tournament, **kwargs): + if instance.verified: + for permit in instance.travelpermit_set.filter( + status=TravelPermit.SYSTEM_WAIT_TOURNAMENT_VERIFICATION + ): + permit.tournament_has_been_verified() + + +post_save.connect(tournament_verified_update_permits, Tournament) diff --git a/events/tasks.py b/events/tasks.py new file mode 100644 index 0000000..d1fb3b6 --- /dev/null +++ b/events/tasks.py @@ -0,0 +1,11 @@ +# Create your tasks here +from __future__ import absolute_import, unicode_literals + +from django.core.management import call_command + +from sportsnet.celery import app + + +@app.task(bind=True) +def get_tournament_listings(self): + call_command("get_tournament_listing") diff --git a/events/tests.py b/events/tests.py new file mode 100644 index 0000000..f8eba62 --- /dev/null +++ b/events/tests.py @@ -0,0 +1,115 @@ +from django.contrib.auth import get_user_model +from django.db.utils import IntegrityError +from django.test import TestCase +from django.urls import reverse +from django.urls.exceptions import NoReverseMatch +from django.utils import timezone + +from .models import Association, Rink, Tournament + +# from core.test_helpers import FixtureBasedTestCase + +User = get_user_model() + + +class AssociationTests(TestCase): + def test_name_uniqueness(self): + Association.objects.create(name="test") + self.assertRaises(IntegrityError, Association.objects.create, name="test") + + def test_name_uniqueness_case_insensitive(self): + Association.objects.create(name="test") + self.assertRaises(IntegrityError, Association.objects.create, name="Test") + + +class RinkTests(TestCase): + def test_name_city_province_uniqueness(self): + Rink.objects.create(name="test", city="test", province="test") + self.assertRaises( + IntegrityError, + Rink.objects.create, + name="test", + city="test", + province="test", + ) + + def test_name_city_province_uniqueness_case_insensitive(self): + Rink.objects.create( + name="test", + city="test", + province="test", + ) + self.assertRaises( + IntegrityError, + Rink.objects.create, + name="Test", + city="test", + province="test", + ) + + +class TournamentViewsTests(TestCase): + def setUp(self) -> None: + self.user = User.objects.create_user(email="test@domain.com", password="12345") + self.client.force_login(self.user) + + self.association = Association.objects.create(name="test") + + return super().setUp() + + def test_tournament_creation(self): + tourn_name = "Test Tournament" + data = { + "name": tourn_name, + "sanction_number": "", + "location": "location", + "start_date": timezone.now().date().strftime("%Y-%m-%d"), + "end_date": "", + "association": self.association.pk, + "website": "", + } + try: + self.client.post(reverse("events:tournament-new"), data=data) + exception_message = False + except NoReverseMatch as e: + exception_message = e + + # NOTE: Remove once travelpermits app is in place + self.assertIn( + "'travelpermits' is not a registered namespace", + str(exception_message), + "If travelpermits has been implemented, remove this check and the preceding try/except block.", + ) + + tournament = Tournament.objects.first() + self.assertEqual(tourn_name, tournament.name) + + def test_tournament_listing(self): + + Tournament.objects.create( + association=self.association, + name="test tournament name", + location="", + start_date=timezone.datetime.now().date(), + ) + + resp = self.client.get(reverse("events:tournaments-list")) + + self.assertEqual(200, resp.status_code) + + self.assertContains(resp, b"test tournament name", 1) + + +class ExhibitionViewsTests(TestCase): + def setUp(self) -> None: + self.user = User.objects.create_user(email="test@domain.com", password="12345") + self.client.force_login(self.user) + + self.association = Association.objects.create(name="test") + return super().setUp() + + def test_exhibition_listing(self): + + resp = self.client.get(reverse("events:exhibitions-list")) + + self.assertEqual(200, resp.status_code) diff --git a/events/urls.py b/events/urls.py new file mode 100644 index 0000000..75b5644 --- /dev/null +++ b/events/urls.py @@ -0,0 +1,63 @@ +from django.urls import path + +from events import views + +app_name = "events" +urlpatterns = [ + path( + "tournaments/", + views.event_list, + {"event_type": "tournament"}, + name="tournaments-list", + ), + path( + "tournaments/new/", + views.event_new, + {"event_type": "tournament"}, + name="tournament-new", + ), + path( + "tournaments//", + views.event_details, + {"event_type": "tournament"}, + name="tournament-details", + ), + path( + "tournaments//verified/", + views.tournament_verification, + name="tournament-verify", + ), + path( + "tournaments//edit/", + views.tournament_edit, + name="tournament-edit", + ), + path( + "exhibitions/", + views.event_list, + {"event_type": "exhibition"}, + name="exhibitions-list", + ), + path( + "exhibitions/new/", + views.event_new, + {"event_type": "exhibition"}, + name="exhibition-new", + ), + path( + "exhibitions//", + views.event_details, + {"event_type": "exhibition"}, + name="exhibition-details", + ), + path( + "exhibitions//edit/", + views.exhibition_edit, + name="exhibition-edit", + ), + path( + "admin/tournaments-needing-verifications/", + views.tournaments_needing_verification, + name="tournament-needs-verification", + ), +] diff --git a/events/views.py b/events/views.py new file mode 100644 index 0000000..5a92a2f --- /dev/null +++ b/events/views.py @@ -0,0 +1,238 @@ +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import ( + HttpResponse, + HttpResponseRedirect, + get_object_or_404, + redirect, + render, + resolve_url, +) +from django.utils import timezone + +from core.utils import redirect_next +from events.forms import NewExhibitionForm, NewTournamentForm +from events.models import Exhibition, Tournament +from team.helpers import add_selected_team + + +@login_required +def event_list(request, event_type="tournament"): + """Lists all events (Tournament or Exhibition) depending on event_type value.""" + + events = Tournament.objects.select_related("association") + if event_type.lower() == "exhibition": + events = Exhibition.objects.select_related("other_team_association", "rink") + + events = events.filter(start_date__gte=timezone.now()) + # .prefetch_related( + # "travelpermit_set" + # ) + + return render( + request, + "events/events.html", + { + "event_type": event_type, + "events": events.order_by("start_date"), + }, + ) + + +@login_required +def event_new(request, event_type): + """Creates a new tournament or exhibition""" + + form = NewTournamentForm(request.POST or None) + if event_type == "exhibition": + form = NewExhibitionForm(request.POST or None) + + if request.method == "POST": + + if form.is_valid(): + instance = form.save(commit=False) # type: Tournament + instance.inserted_by = request.user + instance.source = f"{event_type}_new" + + save_event = True + tourns = None + + if isinstance(instance, Tournament): + + if instance.sanction_number and instance.association.name not in [ + "OTHER" + ]: + + tourns = Tournament.objects.filter( + association=instance.association, + sanction_number=instance.sanction_number, + ) + + save_event = not tourns.exists() + + if save_event: + instance.save() + + elif tourns and tourns.exists(): + instance = tourns.first() + + else: + messages.error(request, "System Error Occurred. Contact the office.") + + if not len(messages.get_messages(request=request)): + # return redirect(f'events:{event_type}-details', event_id=instance.id) + return redirect( + f"travelpermits:permit-new-{event_type}", + event_id=instance.id, + ) + + return render( + request, + "events/event_new.html", + { + "event_type": event_type, + "form": form, + }, + ) + + +@login_required +@add_selected_team +def event_details(request, event_type, event_id, selected): + + if event_type == "exhibition": + event = get_object_or_404( + Exhibition.objects.select_related("other_team_association", "rink"), + pk=event_id, + ) + else: + event = get_object_or_404( + Tournament.objects.select_related("association"), pk=event_id + ) + + team = SportsKeeperTeam(load_ids_from=selected) if selected.TeamId else None + + return render( + request, + "events/event_details.html", + { + "event": event, + "event_type": event_type, + "permits": event.travelpermit_set.all(), + "team_already_has_permit": event.travelpermit_set.filter( + team_id=selected.TeamId + ).exists(), + "team": team, + "team_is_approved": team.is_approved() if team else False, + }, + ) + + +@login_required +def tournament_verification(request, event_id): + """ + Tournaments added by anyone except office personnel will require confirmation + that said tournament exists. Only allow those with permission to validate tournaments here. + """ + + event = get_object_or_404(Tournament, pk=event_id) + + if not event.sanction_number: + if request.method == "POST": + return HttpResponse("Failure: Sanction Number Required.") + messages.info(request, "Tournaments must have sanction number!") + else: + event.has_been_verified() + + if request.method == "POST": + return HttpResponse("Success: Verified!") + + if request.GET.get("next"): + return HttpResponseRedirect(request.GET["next"]) + + return redirect("events:tournament-details", event_id=event.id) + + +@login_required +def tournaments_needing_verification(request): + return render( + request, + "events/tournament_verification.html", + { + "tournaments": Tournament.objects.filter(verified=False), + }, + ) + + +@login_required +def tournament_edit(request, event_id): + """Edits an existing tournament""" + + instance = get_object_or_404(Tournament, pk=event_id) + + if not instance.can_edit(): + messages.error( + request, + "Permit(s) attached to this event have already been submitted to the OMHA.", + ) + return redirect("events:tournament-details", event_id=instance.id) + + form = NewTournamentForm(request.POST or None, instance=instance) + + if request.method == "POST" and form.is_valid(): + + if request.POST.get("delete"): + instance.delete() + return redirect("events:tournaments-list") + else: + form.save() + + return redirect_next(request, "events:tournament-needs-verification") + + return render( + request, + "events/event_new.html", + { + "event_type": "tournament", + "form": form, + "editing": True, + }, + ) + + +@login_required +def exhibition_edit(request, event_id): + """Edits an existing exhibition""" + + instance = get_object_or_404(Exhibition, pk=event_id) + + if not instance.can_edit(): + messages.error( + request, + "Permit(s) attached to this event have already been submitted to the OMHA.", + ) + return redirect("events:exhibition-details", event_id=instance.id) + + form = NewExhibitionForm(request.POST or None, instance=instance) + + if request.method == "POST" and form.is_valid(): + + if request.POST.get("delete"): + instance.delete() + return redirect("events:exhibitions-list") + else: + form.save() + + return redirect_next( + request, resolve_url("events:exhibition-details", event_id=instance.id) + ) + + return render( + request, + "events/event_new.html", + { + "event_type": "exhibition", + "form": form, + "editing": True, + }, + ) diff --git a/project_settings.py b/project_settings.py new file mode 100644 index 0000000..02f454c --- /dev/null +++ b/project_settings.py @@ -0,0 +1,28 @@ +from django.utils.functional import cached_property + + +class ProjSettings(object): + def _value(self, app, name, **kwargs): + from core.models import Settings + + return Settings.get_value(app=app, name=name, **kwargs) + + def _values(self, app): + from core.models import Settings + + return Settings.get_values(app=app) + + @cached_property + def DOMAIN_BASE(self): + return self._value("system", "domain_base") + + @cached_property + def PAYMENT_SETTINGS(self): + return self._values(app="Payments") + + @cached_property + def USE_CELERY_TASKS(self): + return self._value("system", "use_tasks", system_specific=True, default=True) + + +proj_settings = ProjSettings() diff --git a/requirements.txt b/requirements.txt index 3d6df38..f2d95bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ django django-allauth +django-bootstrap-form django-crispy-forms django-environ django-model-utils @@ -16,3 +17,5 @@ loguru psycopg2-binary requests sentry-sdk +selenium +pytz diff --git a/sportsnet/settings.py b/sportsnet/settings.py index 3e90e22..8cf3825 100644 --- a/sportsnet/settings.py +++ b/sportsnet/settings.py @@ -48,6 +48,7 @@ "django_templated_emailer", "phonenumber_field", "reversion", + "events", ] if env.bool("DJANGO_LOAD_EXTRA_EXTENSIONS", False): # pragma: no cover diff --git a/sportsnet/urls.py b/sportsnet/urls.py index d962643..b682987 100644 --- a/sportsnet/urls.py +++ b/sportsnet/urls.py @@ -5,6 +5,7 @@ path("admin/", admin.site.urls), path("accounts/", include("allauth.urls")), path("team/", include("team.urls")), + path("events", include("events.urls")), # Always keep last! path("", include("core.urls")), ] diff --git a/templates/core/index.html b/templates/core/index.html index 4ffe3d1..447af6e 100644 --- a/templates/core/index.html +++ b/templates/core/index.html @@ -1,6 +1,10 @@ -{% extends 'core/root.html' %} +{% extends 'root.html' %} {% block content %} hello world! - Team + {% endblock %} diff --git a/templates/events/__init__.py b/templates/events/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/templates/events/base.html b/templates/events/base.html new file mode 100644 index 0000000..449c0a3 --- /dev/null +++ b/templates/events/base.html @@ -0,0 +1,2 @@ +{% extends 'root.html' %} +{% block site_title %}Tournaments{% endblock %} \ No newline at end of file diff --git a/templates/events/event-merge.html b/templates/events/event-merge.html new file mode 100644 index 0000000..0bdb473 --- /dev/null +++ b/templates/events/event-merge.html @@ -0,0 +1,64 @@ +{% extends 'events/base.html' %} +{% load request_posted_tools %} + +{% block content %} +
+
+
Tournament Merging
+
+
+ +
{% csrf_token %} + +
+ +
+
+ (internal id) - sanction number - association name - tournament name - source - count of permits using this tournament +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +{% endblock %} diff --git a/templates/events/event_details.html b/templates/events/event_details.html new file mode 100644 index 0000000..738e3bf --- /dev/null +++ b/templates/events/event_details.html @@ -0,0 +1,191 @@ +{% extends 'events/base.html' %} + +{% block site_title %}{{ event_type|title }} Details - {{ event.name }}{% endblock %} +{% block page_title %}{% endblock %} + +{% block content %} + + +
+
{{ event_type|title }} Details + {% if event.can_edit and request.user.is_staff %} + {% if event_type == 'exhibition' %} + Edit + {% else %} + Edit + {% endif %} + {% endif %} +
+
+ + {% if event_type == 'tournament' %} + +
+
Association
+
+ {{ event.association.name }} +
+
+ +
+
Name
+
+ {{ event.name }} +
+
+ +
+
Sanction Number
+
+ {{ event.sanction_number }} +
+
+ +
+
Location
+
+ {{ event.location }} +
+
+ +
+
Start Date
+
+ {{ event.start_date }} +
+
+ +
+
End Date
+
+ {% if event.end_date %}{{ event.end_date }}{% endif %} +
+
+ +
+
Verified
+
+ {{ event.verified }} +
+
+ +
+
Divisions
+
+ {{ event.divisions }} +
+
+ + {% else %} + +
+
Other Team
+
+ {{ event.other_team }} ({{ event.get_other_team_association }}) +
+
+ +
+
Destination
+
+ {{ event.destination }} +
+
+ +
+
Rink/Arena
+
+ {{ event.get_arena }} +
+
+ +
+
Start Date and Time
+
+ {{ event.start_date }} +
+
+ +
+
End Date and Time
+
+ {{ event.end_datetime }} +
+
+ + {% endif %} + + {% if event_type == 'tournament' and not team_already_has_permit or event_type == 'exhibition' and not permits.count %} + + {% if choice_selected.TeamId %} +

+ + Request permit to attend this {{ event_type|title }} +

+ {% else %} + + +

+ To request a permit for this {{ event_type }}, + {% if event_type == 'tournament' %} + you must select a team first. + {% else %} + you must select a team first. + {% endif %} +

+ + + {% endif %} + {% else %} +

You already have a permit for this event, only 1 is allowed. Contact the office if you require another.

+ {% endif %} + + + +
+
+ + +
+
+
+
+
+ + Teams Attending: {% if event_type == 'tournament' %}{{ event.name }}{% else %}{{ event.other_team }}{% endif %} + +
+
+
+
+ + + + + + + + + + + + + {% for permit in permits %} + + + + + + + + {% endfor %} + + +
TeamStatusLeagueDivisionSubDivision
{{ permit.team }}{{ permit.status }}{{ permit.league }}{{ permit.division }}{{ permit.subdivision }}
+
+
+
+
+ +{% endblock %} diff --git a/templates/events/event_new.html b/templates/events/event_new.html new file mode 100644 index 0000000..bcb381d --- /dev/null +++ b/templates/events/event_new.html @@ -0,0 +1,200 @@ +{% extends 'events/base.html' %} +{% load static %} + +{% block site_title %}New {{ event_type|title }}{% endblock %} +{% block page_title %}{% endblock %} + +{% block content %} + +
+
+
+ + New {{ event_type|title }} +
+
+
+

Please supply the information below to add your {{ event_type|title }}

+
+ {% csrf_token %} +
+ + {% if event_type == 'tournament' %} + {{ form.name }} + {{ form.association }} +
+ {{ form.association_other }} +
+ {{ form.sanction_number }} + {{ form.location }} + {{ form.start_date }} + {{ form.end_date }} + {{ form.website }} + {% else %} + + {{ form.other_team }} + {{ form.other_team_association }} +
+ {{ form.other_team_association_other }} +
+ {{ form.destination }} + + {{ form.rink }} + +
+ {{ form.arena }} +
+ + {{ form.start_date }} + {{ form.end_datetime }} + +
+ {{ form.required_referee_or_timekeeper }} + +
+ +
+ {{ form.referee_requirements }} + + {{ form.cell_phone }} + {{ form.contact_name }} + +
+ {{ form.timekeeper_needed }} + +
+ +
+ {{ form.req_ack }} + + {{ form.req_ack.errors }} +
+ +
+ +
+ {{ form.timekeeper_notes_inline }} +
+
+
+ + {% endif %} + +
+ +
+ + + {% if form.instance.pk and request.user.is_staff %} + + {% endif %} +
+
+
+
+ + +{% endblock %} + +{% block footer %} + + + +{% endblock %} diff --git a/templates/events/events.html b/templates/events/events.html new file mode 100644 index 0000000..2e751ff --- /dev/null +++ b/templates/events/events.html @@ -0,0 +1,94 @@ +{% extends 'events/base.html' %} + +{% block site_title %}{{ event_type|title }} Listing{% endblock %} +{% block page_title %}{{ event_type|title }} Listing{% endblock %} + +{% block content %} + +
+
+
+ Information +
+
+
+ +

Use the search box to find the {{ event_type }} and click on its name to continue

+

+ Can't find the {{ event_type }}? Click here to continue +

+ +
+
+ +
+
+
+
+
+ + {{ event_type|title }} Listing +
+
+
+
+ + + + {% if event_type == 'tournament' %} + + + + + + + + + {% else %} + + + + + {% endif %} + + + + + + {% for event in events %} + + + {% if event_type == 'tournament' %} + + + + + + + + + {% else %} + + + + + {% endif %} + + + + {% endfor %} + + +
Start DateNameAssociationSanctionLocationVerifiedDivisionWebsiteStart DateOther TeamDestinationArenaTeams
{{ event.start_date|date:"Y-m-d" }}{{ event.name }}{{ event.association.name }}{{ event.sanction_number }}{{ event.location }}{{ event.verified|yesno:"Verified,Unverified" }}{{ event.divisions }} + {% if event.website %} + {{ event.website }} + {% endif %} + {{ event.start_date|date:"Y-m-d" }}{{ event.other_team }}{{ event.destination }}{{ event.get_arena }}{% if event.travelpermit_set.exists %}{{ event.travelpermit_set.count }}{% endif %}
+
+
+
+ +
+ +{% endblock %} diff --git a/templates/events/tournament_verification.html b/templates/events/tournament_verification.html new file mode 100644 index 0000000..d5809fe --- /dev/null +++ b/templates/events/tournament_verification.html @@ -0,0 +1,73 @@ +{% extends 'events/base.html' %} + +{% block site_title %}Tournament Verification{% endblock %} +{% block page_title %}Tournament Verification{% endblock %} + +{% block content %} + +
+
+
+
+
+ + Tournament Verification +
+
+
+
+

+ This page shows tournaments that have been manually added to the system by a coach. We require + that someone else verify the tournament exists and that the information provided is correct + (proper sanction number, location, start/end times...etc). +

+

+ Once that information has been verified, you can click on "Verify data is correct" and any + travel permits waiting for verification will be queued for submission to the OMHA +

+ + + + + + + + + + + + + + + + + {% for event in tournaments %} + + + + + + + + + + + + {% endfor %} + + +
Start DateNameAssociationSanctionLocationVerifiedErrorsDivisionWebsite
{{ event.start_date|date:"Y-m-d" }}{{ event.name }}{{ event.association.name }}{% if event.association_other %} - {{ event.association_other }}{% endif %}{{ event.sanction_number }}{{ event.location }}Data Is CorrectEdit Errors{{ event.divisions }} + {% if event.source != 'tournament_listing_url' %} + {{ event.website }} + {% endif %} +
+
+
+
+ +
+ +{% endblock %} diff --git a/templates/core/root.html b/templates/root.html similarity index 100% rename from templates/core/root.html rename to templates/root.html diff --git a/tox.ini b/tox.ini index 2d2cb1d..8be0d0c 100644 --- a/tox.ini +++ b/tox.ini @@ -53,7 +53,7 @@ commands = max-line-length=120 exclude = migrations - .venv + *venv* .vscode .tox .github @@ -61,6 +61,7 @@ exclude = generate_test_fixture_data.py generate_test_fixture_data_local_extras.py */settings_test.py + events/* exclude_lines = pragma: no cover extend_ignore=E722