From 9445940df27d05c56f0a71dd22743cca948e6340 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 20 Nov 2021 18:53:21 -0600 Subject: [PATCH 01/10] initial commit --- benchcoach/settings.py | 1 + benchcoach/urls.py | 3 +- teamsnap/__init__.py | 0 teamsnap/admin.py | 9 ++ teamsnap/apps.py | 6 + teamsnap/migrations/0001_initial.py | 82 +++++++++++++ .../migrations/0002_auto_20211121_0035.py | 43 +++++++ teamsnap/migrations/__init__.py | 0 teamsnap/models.py | 33 ++++++ teamsnap/teamsnap/__init__.py | 3 + teamsnap/teamsnap/api.py | 109 ++++++++++++++++++ teamsnap/templates/teamsnap/event_list.html | 23 ++++ teamsnap/tests.py | 3 + teamsnap/urls.py | 9 ++ teamsnap/views.py | 15 +++ 15 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 teamsnap/__init__.py create mode 100644 teamsnap/admin.py create mode 100644 teamsnap/apps.py create mode 100644 teamsnap/migrations/0001_initial.py create mode 100644 teamsnap/migrations/0002_auto_20211121_0035.py create mode 100644 teamsnap/migrations/__init__.py create mode 100644 teamsnap/models.py create mode 100644 teamsnap/teamsnap/__init__.py create mode 100644 teamsnap/teamsnap/api.py create mode 100644 teamsnap/templates/teamsnap/event_list.html create mode 100644 teamsnap/tests.py create mode 100644 teamsnap/urls.py create mode 100644 teamsnap/views.py diff --git a/benchcoach/settings.py b/benchcoach/settings.py index df7fdd6..6ac34c7 100644 --- a/benchcoach/settings.py +++ b/benchcoach/settings.py @@ -36,6 +36,7 @@ INSTALLED_APPS = [ 'venues.apps.VenuesConfig', 'players.apps.PlayersConfig', 'lineups.apps.LineupsConfig', + 'teamsnap.apps.TeamsnapConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', diff --git a/benchcoach/urls.py b/benchcoach/urls.py index d7b5795..64d4e33 100644 --- a/benchcoach/urls.py +++ b/benchcoach/urls.py @@ -27,5 +27,6 @@ urlpatterns = [ path('teams/', include('teams.urls')), path('venues/', include('venues.urls')), path('players/', include('players.urls')), - path('lineups/', include('lineups.urls')) + path('lineups/', include('lineups.urls')), +path('teamsnap/', include('teamsnap.urls')) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/teamsnap/__init__.py b/teamsnap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/teamsnap/admin.py b/teamsnap/admin.py new file mode 100644 index 0000000..d73b133 --- /dev/null +++ b/teamsnap/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from .models import User, Team, Location, Event, Member + +# Register your models here. +admin.site.register(User) +admin.site.register(Team) +admin.site.register(Event) +admin.site.register(Location) +admin.site.register(Member) \ No newline at end of file diff --git a/teamsnap/apps.py b/teamsnap/apps.py new file mode 100644 index 0000000..ba18c28 --- /dev/null +++ b/teamsnap/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TeamsnapConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'teamsnap' diff --git a/teamsnap/migrations/0001_initial.py b/teamsnap/migrations/0001_initial.py new file mode 100644 index 0000000..d5ee178 --- /dev/null +++ b/teamsnap/migrations/0001_initial.py @@ -0,0 +1,82 @@ +# Generated by Django 3.2.6 on 2021-11-20 23:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('teams', '0001_initial'), + ('players', '0003_player_team'), + ('venues', '0001_initial'), + ('events', '0004_delete_availability'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('teamsnap_id', models.CharField(max_length=10)), + ('access_token', models.CharField(max_length=50)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('teamsnap_id', models.CharField(max_length=10)), + ('name', models.CharField(max_length=50, null=True)), + ('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teams.team')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Member', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('teamsnap_id', models.CharField(max_length=10)), + ('name', models.CharField(max_length=50, null=True)), + ('player', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='players.player')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Location', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('teamsnap_id', models.CharField(max_length=10)), + ('name', models.CharField(max_length=50, null=True)), + ('venue', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='venues.venue')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('teamsnap_id', models.CharField(max_length=10)), + ('name', models.CharField(max_length=50, null=True)), + ('label', models.CharField(max_length=50, null=True)), + ('start_date', models.DateTimeField(null=True)), + ('event', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='events.event')), + ('location', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.location')), + ('opponent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/teamsnap/migrations/0002_auto_20211121_0035.py b/teamsnap/migrations/0002_auto_20211121_0035.py new file mode 100644 index 0000000..7d9b4c8 --- /dev/null +++ b/teamsnap/migrations/0002_auto_20211121_0035.py @@ -0,0 +1,43 @@ +# Generated by Django 3.2.6 on 2021-11-21 00:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='formatted_title', + field=models.CharField(max_length=50, null=True), + ), + migrations.AlterField( + model_name='event', + name='teamsnap_id', + field=models.CharField(max_length=10, unique=True), + ), + migrations.AlterField( + model_name='location', + name='teamsnap_id', + field=models.CharField(max_length=10, unique=True), + ), + migrations.AlterField( + model_name='member', + name='teamsnap_id', + field=models.CharField(max_length=10, unique=True), + ), + migrations.AlterField( + model_name='team', + name='teamsnap_id', + field=models.CharField(max_length=10, unique=True), + ), + migrations.AlterField( + model_name='user', + name='teamsnap_id', + field=models.CharField(max_length=10, unique=True), + ), + ] diff --git a/teamsnap/migrations/__init__.py b/teamsnap/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/teamsnap/models.py b/teamsnap/models.py new file mode 100644 index 0000000..3d960ec --- /dev/null +++ b/teamsnap/models.py @@ -0,0 +1,33 @@ +from django.db import models +import teams.models +import venues.models +import players.models +import events.models + +class TeamsnapBaseModel(models.Model): + teamsnap_id = models.CharField(max_length=10, unique=True) + name = models.CharField(max_length=50, null=True) + + class Meta: + abstract = True + +class User(TeamsnapBaseModel): + access_token = models.CharField(max_length = 50) + name = None + +class Team(TeamsnapBaseModel): + team = models.ForeignKey(teams.models.Team, null=True, on_delete=models.CASCADE) + +class Location(TeamsnapBaseModel): + venue = models.ForeignKey(venues.models.Venue, null=True, on_delete=models.CASCADE) + +class Member(TeamsnapBaseModel): + player = models.ForeignKey(players.models.Player, null=True, on_delete=models.CASCADE) + +class Event(TeamsnapBaseModel): + event = models.ForeignKey(events.models.Event, null=True, on_delete=models.CASCADE) + label = models.CharField(max_length = 50, null=True) + start_date = models.DateTimeField(null=True) + opponent = models.ForeignKey(Team, null=True, on_delete=models.CASCADE) + location = models.ForeignKey(Location, null=True, on_delete=models.CASCADE) + formatted_title = models.CharField(max_length = 50, null=True) \ No newline at end of file diff --git a/teamsnap/teamsnap/__init__.py b/teamsnap/teamsnap/__init__.py new file mode 100644 index 0000000..31d7a75 --- /dev/null +++ b/teamsnap/teamsnap/__init__.py @@ -0,0 +1,3 @@ +from .api import TeamSnap + +__all__ = ['TeamSnap'] \ No newline at end of file diff --git a/teamsnap/teamsnap/api.py b/teamsnap/teamsnap/api.py new file mode 100644 index 0000000..1eaa44f --- /dev/null +++ b/teamsnap/teamsnap/api.py @@ -0,0 +1,109 @@ +__all__ = ['TeamSnap', 'Team', 'Event', 'Availability', 'Member', 'Location', 'Me'] +from apiclient import APIClient, HeaderAuthentication, JsonResponseHandler + + +class ApiObject(): + rel = None + + def __init__(self, client, rel=rel, data={}): + self.client = client + self.data = data + self.rel = rel + + @classmethod + def search(cls, client, **kwargs): + results = client.query(cls.rel, "search", **kwargs) + return [cls(client,rel=cls.rel, data=r) for r in results] + + @classmethod + def get(cls, client, id): + r = client.get(f"{client.link(cls.rel)}/{id}") + return cls(client, cls.rel, client.parse_response(r)[0]) + +class Me (ApiObject): + rel = "me" + + def __init__(self, client): + super().__init__(client=client, rel=self.rel, data=client.get(client.link(self.rel))) + +class Event (ApiObject): + rel = "events" + +class Team (ApiObject): + rel = "teams" + pass + +class Availability (ApiObject): + rel = "availabilities" + pass + +class Member (ApiObject): + rel = "members" + +class Location (ApiObject): + rel = "locations" + +class Opponent (ApiObject): + rel = "opponents" + +class TeamSnap(APIClient): + base_url = 'https://api.teamsnap.com/v3' + + def __init__(self, token, *args, **kwargs): + super().__init__(*args, + authentication_method=HeaderAuthentication(token=token), + response_handler=JsonResponseHandler, + **kwargs) + self._root_collection = self.get(self.base_url)['collection'] + self._links = self._by_rel(self.base_url, 'links') + self._queries = self._by_rel(self.base_url, 'queries') + self._commands = self._by_rel(self.base_url, 'commands') + pass + + def link(self, link_name): + d = {l['rel']:l['href'] for l in self._root_collection["links"]} + return d.get(link_name) + + def _by_rel (self, url, k): + try: + {l['rel']: l for l in self._root_collection[k]} + except Exception as e: + return {} + self.get(url)['collection'][k] + return {l['rel']:l for l in self.get(url)['collection'][k]} + + def query (self, rel, query, **kwargs): + queries = self._by_rel(self._get_href(rel), 'queries') + response = self.get(self._get_href(query, queries), params=kwargs) + return self.parse_response(response) + + def command (self, rel, command, **kwargs): + commands = self._by_rel(self._get_href(rel), 'commands') + response = self.get(self._get_href(command, commands), params=kwargs) + return self.parse_response(response) + + def _get_href (self, rel: str, links:dict = None, url = base_url) -> str: + """returns a hyperlink from a the links dictionary. Each item in the links dictionary is a + dictionary with a rel and href key""" + if links is None: links = self._by_rel(url, 'links') + link = links[rel]['href'] + return link + + def get_item (self, rel, id): + r = self.get(f"{self.link(rel)}/{id}") + return self.parse_response(r)[0] + + @classmethod + def parse_response(self, response): + result = [] + items = [item['data'] for item in response['collection'].get('items',[])] + for item in response['collection'].get('items',[]): + details = {} + for detail in item['data']: + # TODO type casting and validation based on item['type'] + details[detail['name']] = detail['value'] + result.append(details) + + return result + # return [{detail['name']: detail['value'] for detail in item} for item in items] + diff --git a/teamsnap/templates/teamsnap/event_list.html b/teamsnap/templates/teamsnap/event_list.html new file mode 100644 index 0000000..3da4c94 --- /dev/null +++ b/teamsnap/templates/teamsnap/event_list.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% block title %} {{ title }}{% endblock %} + +{% block content %} + +

{{ title }}

+
    +{% for item in object_list %} +
  1. + {{ item.formatted_title }} + {{ item.subtitle }} +{# {% if item.body %}#} +{#
    {{ item.body }}#} +
    {{ item.start_date|date:"D, M j, g:i A" }},
    {{item.location.name}} +{# {% endif %}#} +
    + {% for button in item.buttons %} + {{ button.label }} + {% endfor %} +
  2. +{% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/teamsnap/tests.py b/teamsnap/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/teamsnap/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/teamsnap/urls.py b/teamsnap/urls.py new file mode 100644 index 0000000..fdfa498 --- /dev/null +++ b/teamsnap/urls.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from django.urls import path, include + +from . import views + +urlpatterns = [ + path('events', views.EventsListView.as_view(), name="teamsnap events list") +] \ No newline at end of file diff --git a/teamsnap/views.py b/teamsnap/views.py new file mode 100644 index 0000000..01b55d6 --- /dev/null +++ b/teamsnap/views.py @@ -0,0 +1,15 @@ +from django.shortcuts import render + +# from .teamsnap.api import TeamSnap, Team, Event, Availability +from .models import User, Member, Team, Event, Location +from django.views.generic.list import ListView + + +class EventsListView(ListView): + model = Event + +class TeamListView(ListView): + model = Team + +class LocationListView(ListView): + model = Location \ No newline at end of file From 05232c7a313ee6bb5287b5c877a7f379739f8148 Mon Sep 17 00:00:00 2001 From: Tony Date: Sun, 21 Nov 2021 12:28:47 -0600 Subject: [PATCH 02/10] initial commit --- .../0007_alter_positioning_position.py | 18 ++++ teamsnap/admin.py | 5 +- .../migrations/0003_auto_20211121_1540.py | 60 +++++++++++++ .../migrations/0004_remove_member_name.py | 17 ++++ teamsnap/migrations/0005_availability.py | 30 +++++++ .../0006_alter_availability_status_code.py | 18 ++++ .../migrations/0007_auto_20211121_1628.py | 24 +++++ .../0008_alter_availability_options.py | 17 ++++ .../migrations/0009_auto_20211121_1757.py | 23 +++++ teamsnap/migrations/0010_event_is_game.py | 19 ++++ teamsnap/models.py | 88 +++++++++++++++++-- teamsnap/teamsnap/api.py | 3 + teamsnap/templates/teamsnap/event_list.html | 8 +- teamsnap/urls.py | 4 +- teamsnap/views.py | 26 +++++- 15 files changed, 344 insertions(+), 16 deletions(-) create mode 100644 lineups/migrations/0007_alter_positioning_position.py create mode 100644 teamsnap/migrations/0003_auto_20211121_1540.py create mode 100644 teamsnap/migrations/0004_remove_member_name.py create mode 100644 teamsnap/migrations/0005_availability.py create mode 100644 teamsnap/migrations/0006_alter_availability_status_code.py create mode 100644 teamsnap/migrations/0007_auto_20211121_1628.py create mode 100644 teamsnap/migrations/0008_alter_availability_options.py create mode 100644 teamsnap/migrations/0009_auto_20211121_1757.py create mode 100644 teamsnap/migrations/0010_event_is_game.py diff --git a/lineups/migrations/0007_alter_positioning_position.py b/lineups/migrations/0007_alter_positioning_position.py new file mode 100644 index 0000000..08c686c --- /dev/null +++ b/lineups/migrations/0007_alter_positioning_position.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-21 16:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('lineups', '0006_alter_positioning_order'), + ] + + operations = [ + migrations.AlterField( + model_name='positioning', + name='position', + field=models.CharField(blank=True, choices=[('EH', 'EH'), ('P', 'P'), ('C', 'C'), ('1B', '1B'), ('2B', '2B'), ('3B', '3B'), ('SS', 'SS'), ('LF', 'LF'), ('CF', 'CF'), ('RF', 'RF'), ('DH', 'DH')], default=None, max_length=2, null=True), + ), + ] diff --git a/teamsnap/admin.py b/teamsnap/admin.py index d73b133..b5e2280 100644 --- a/teamsnap/admin.py +++ b/teamsnap/admin.py @@ -1,9 +1,10 @@ from django.contrib import admin -from .models import User, Team, Location, Event, Member +from .models import User, Team, Location, Event, Member, Availability # Register your models here. admin.site.register(User) admin.site.register(Team) admin.site.register(Event) admin.site.register(Location) -admin.site.register(Member) \ No newline at end of file +admin.site.register(Member) +admin.site.register(Availability) \ No newline at end of file diff --git a/teamsnap/migrations/0003_auto_20211121_1540.py b/teamsnap/migrations/0003_auto_20211121_1540.py new file mode 100644 index 0000000..2b78008 --- /dev/null +++ b/teamsnap/migrations/0003_auto_20211121_1540.py @@ -0,0 +1,60 @@ +# Generated by Django 3.2.6 on 2021-11-21 15:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0002_auto_20211121_0035'), + ] + + operations = [ + migrations.RenameField( + model_name='event', + old_name='event', + new_name='bencoach_event', + ), + migrations.RenameField( + model_name='location', + old_name='venue', + new_name='bencoach_venue', + ), + migrations.RenameField( + model_name='member', + old_name='player', + new_name='bencoach_player', + ), + migrations.RenameField( + model_name='team', + old_name='team', + new_name='bencoach_team', + ), + migrations.AddField( + model_name='member', + name='first_name', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='member', + name='is_non_player', + field=models.BooleanField(default=False), + preserve_default=False, + ), + migrations.AddField( + model_name='member', + name='jersey_number', + field=models.IntegerField(null=True), + ), + migrations.AddField( + model_name='member', + name='last_name', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='member', + name='team', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team'), + ), + ] diff --git a/teamsnap/migrations/0004_remove_member_name.py b/teamsnap/migrations/0004_remove_member_name.py new file mode 100644 index 0000000..19dff06 --- /dev/null +++ b/teamsnap/migrations/0004_remove_member_name.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.6 on 2021-11-21 15:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0003_auto_20211121_1540'), + ] + + operations = [ + migrations.RemoveField( + model_name='member', + name='name', + ), + ] diff --git a/teamsnap/migrations/0005_availability.py b/teamsnap/migrations/0005_availability.py new file mode 100644 index 0000000..02e08c5 --- /dev/null +++ b/teamsnap/migrations/0005_availability.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.6 on 2021-11-21 16:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('lineups', '0007_alter_positioning_position'), + ('teamsnap', '0004_remove_member_name'), + ] + + operations = [ + migrations.CreateModel( + name='Availability', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('teamsnap_id', models.CharField(max_length=10, unique=True)), + ('status_code', models.SmallIntegerField(null=True)), + ('benchcoach_availability', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='lineups.availability')), + ('event', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.event')), + ('member', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.member')), + ('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/teamsnap/migrations/0006_alter_availability_status_code.py b/teamsnap/migrations/0006_alter_availability_status_code.py new file mode 100644 index 0000000..24aeb64 --- /dev/null +++ b/teamsnap/migrations/0006_alter_availability_status_code.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-21 16:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0005_availability'), + ] + + operations = [ + migrations.AlterField( + model_name='availability', + name='status_code', + field=models.SmallIntegerField(choices=[(1, 'Yes'), (0, 'No'), (2, 'Maybe'), (None, 'Unknown')], default=None, null=True), + ), + ] diff --git a/teamsnap/migrations/0007_auto_20211121_1628.py b/teamsnap/migrations/0007_auto_20211121_1628.py new file mode 100644 index 0000000..638c677 --- /dev/null +++ b/teamsnap/migrations/0007_auto_20211121_1628.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.6 on 2021-11-21 16:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0006_alter_availability_status_code'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='team', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team'), + ), + migrations.AlterField( + model_name='event', + name='opponent', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opponent', to='teamsnap.team'), + ), + ] diff --git a/teamsnap/migrations/0008_alter_availability_options.py b/teamsnap/migrations/0008_alter_availability_options.py new file mode 100644 index 0000000..a9e2671 --- /dev/null +++ b/teamsnap/migrations/0008_alter_availability_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.6 on 2021-11-21 16:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0007_auto_20211121_1628'), + ] + + operations = [ + migrations.AlterModelOptions( + name='availability', + options={'verbose_name_plural': 'availabilities'}, + ), + ] diff --git a/teamsnap/migrations/0009_auto_20211121_1757.py b/teamsnap/migrations/0009_auto_20211121_1757.py new file mode 100644 index 0000000..6b49c38 --- /dev/null +++ b/teamsnap/migrations/0009_auto_20211121_1757.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.6 on 2021-11-21 17:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0008_alter_availability_options'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='points_for_opponent', + field=models.PositiveSmallIntegerField(null=True), + ), + migrations.AddField( + model_name='event', + name='points_for_team', + field=models.PositiveSmallIntegerField(null=True), + ), + ] diff --git a/teamsnap/migrations/0010_event_is_game.py b/teamsnap/migrations/0010_event_is_game.py new file mode 100644 index 0000000..a7fde38 --- /dev/null +++ b/teamsnap/migrations/0010_event_is_game.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.6 on 2021-11-21 18:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0009_auto_20211121_1757'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='is_game', + field=models.BooleanField(default=False), + preserve_default=False, + ), + ] diff --git a/teamsnap/models.py b/teamsnap/models.py index 3d960ec..45cb086 100644 --- a/teamsnap/models.py +++ b/teamsnap/models.py @@ -1,4 +1,6 @@ from django.db import models + +import lineups.models import teams.models import venues.models import players.models @@ -11,23 +13,97 @@ class TeamsnapBaseModel(models.Model): class Meta: abstract = True + def __str__(self): + return f"{self.name} ({self.teamsnap_id})" + class User(TeamsnapBaseModel): access_token = models.CharField(max_length = 50) name = None + def __str__(self): + return f"{self.teamsnap_id}" + class Team(TeamsnapBaseModel): - team = models.ForeignKey(teams.models.Team, null=True, on_delete=models.CASCADE) + bencoach_team = models.ForeignKey(teams.models.Team, null=True, on_delete=models.CASCADE) + + @property + def view_url(self): + return f"https://go.teamsnap.com/{self.team.teamsnap_id}/team/view/{self.teamsnap_id}" + + @property + def edit_url(self): + return f"https://go.teamsnap.com/{self.team.teamsnap_id}/team/edit/{self.teamsnap_id}" class Location(TeamsnapBaseModel): - venue = models.ForeignKey(venues.models.Venue, null=True, on_delete=models.CASCADE) + bencoach_venue = models.ForeignKey(venues.models.Venue, null=True, on_delete=models.CASCADE) + + @property + def view_url(self): + return f"https://go.teamsnap.com/{self.team.teamsnap_id}/location/view/{self.teamsnap_id}" + + @property + def edit_url(self): + return f"https://go.teamsnap.com/{self.team.teamsnap_id}/location/edit/{self.teamsnap_id}" class Member(TeamsnapBaseModel): - player = models.ForeignKey(players.models.Player, null=True, on_delete=models.CASCADE) + name = None + bencoach_player = models.ForeignKey(players.models.Player, null=True, on_delete=models.CASCADE) + team = models.ForeignKey(Team, null=True, on_delete=models.CASCADE) + first_name = models.CharField(max_length = 50, null=True) + last_name = models.CharField(max_length = 50, null=True) + jersey_number = models.IntegerField(null=True) + is_non_player = models.BooleanField() + + def __str__(self): + return f"{self.last_name}, {self.first_name} ({self.teamsnap_id})" + + @property + def view_url(self): + return f"https://go.teamsnap.com/{self.team.teamsnap_id}/roster/player/{self.teamsnap_id}" + + @property + def edit_url(self): + return f"https://go.teamsnap.com/{self.team.teamsnap_id}/roster/edit/{self.teamsnap_id}" class Event(TeamsnapBaseModel): - event = models.ForeignKey(events.models.Event, null=True, on_delete=models.CASCADE) + bencoach_event = models.ForeignKey(events.models.Event, null=True, on_delete=models.CASCADE) label = models.CharField(max_length = 50, null=True) start_date = models.DateTimeField(null=True) - opponent = models.ForeignKey(Team, null=True, on_delete=models.CASCADE) + opponent = models.ForeignKey(Team, null=True, on_delete=models.CASCADE, related_name="opponent") + team = models.ForeignKey(Team, null=True, on_delete=models.CASCADE) location = models.ForeignKey(Location, null=True, on_delete=models.CASCADE) - formatted_title = models.CharField(max_length = 50, null=True) \ No newline at end of file + formatted_title = models.CharField(max_length = 50, null=True) + points_for_opponent = models.PositiveSmallIntegerField(null=True) + points_for_team = models.PositiveSmallIntegerField(null=True) + is_game = models.BooleanField() + + @property + def view_url(self): + return f"https://go.teamsnap.com/{self.team.teamsnap_id}/schedule/view_game/{self.teamsnap_id}" + + @property + def edit_url(self): + return f"https://go.teamsnap.com/{self.team.teamsnap_id}/schedule/edit_game/{self.teamsnap_id}" + + def __str__(self): + return f"{self.formatted_title} ({self.teamsnap_id})" + +class Availability(TeamsnapBaseModel): + status_codes = [ + (1, 'Yes'), + (0, 'No'), + (2, 'Maybe'), + (None, 'Unknown') + ] + name = None + team = models.ForeignKey(Team, null=True, on_delete=models.CASCADE) + event = models.ForeignKey(Event, null=True, on_delete=models.CASCADE) + member = models.ForeignKey(Member, null=True, on_delete=models.CASCADE) + benchcoach_availability = models.ForeignKey(lineups.models.Availability, null=True, on_delete=models.CASCADE) + status_code = models.SmallIntegerField(null=True, choices=status_codes, default=None) + + def __str__(self): + return f"{self.member} - {self.event} ({self.teamsnap_id})" + + class Meta: + verbose_name_plural = "availabilities" \ No newline at end of file diff --git a/teamsnap/teamsnap/api.py b/teamsnap/teamsnap/api.py index 1eaa44f..2386993 100644 --- a/teamsnap/teamsnap/api.py +++ b/teamsnap/teamsnap/api.py @@ -46,6 +46,9 @@ class Location (ApiObject): class Opponent (ApiObject): rel = "opponents" +class EventLineupEntry (ApiObject): + rel = "event_lineup_entries" + class TeamSnap(APIClient): base_url = 'https://api.teamsnap.com/v3' diff --git a/teamsnap/templates/teamsnap/event_list.html b/teamsnap/templates/teamsnap/event_list.html index 3da4c94..c973204 100644 --- a/teamsnap/templates/teamsnap/event_list.html +++ b/teamsnap/templates/teamsnap/event_list.html @@ -4,10 +4,10 @@ {% block content %}

{{ title }}

-
    +{#
      #} {% for item in object_list %} -
    1. - {{ item.formatted_title }} +
        + {{ item.formatted_title }} {{ item.subtitle }} {# {% if item.body %}#} {#
        {{ item.body }}#} @@ -17,7 +17,7 @@ {% for button in item.buttons %} {{ button.label }} {% endfor %} - +
      {% endfor %}
    {% endblock %} \ No newline at end of file diff --git a/teamsnap/urls.py b/teamsnap/urls.py index fdfa498..a8e65a1 100644 --- a/teamsnap/urls.py +++ b/teamsnap/urls.py @@ -1,9 +1,11 @@ from django.contrib import admin from django.urls import path, include +from functools import partial from . import views urlpatterns = [ - path('events', views.EventsListView.as_view(), name="teamsnap events list") + path('events', views.EventsListView.as_view(), name="teamsnap list events"), + path('edit/event/', views.edit_event, name='teamsnap edit event') ] \ No newline at end of file diff --git a/teamsnap/views.py b/teamsnap/views.py index 01b55d6..e4e3da4 100644 --- a/teamsnap/views.py +++ b/teamsnap/views.py @@ -1,12 +1,32 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect # from .teamsnap.api import TeamSnap, Team, Event, Availability from .models import User, Member, Team, Event, Location from django.views.generic.list import ListView +from lib.views import BenchcoachListView +def edit_event(request, id): + event = Event.objects.get(id = id) + return redirect(event.edit_url) -class EventsListView(ListView): - model = Event +class EventsListView(BenchcoachListView): + Model = Event + edit_url = 'teamsnap edit event' + list_url = 'teamsnap list events' + page_title = "TeamSnap Events" + title_strf = '{item.formatted_title}' + body_strf = "{item.start_date:%a, %b %-d, %-I:%M %p},\n{item.location.name}" + + # def get_context_data(self): + # context = super().get_context_data() + # for item in context['items']: + # item['buttons'].append( + # { + # 'label': 'Edit Lineup', + # 'href': reverse('edit lineup', args=[item['id']]) + # } + # ) + # return context class TeamListView(ListView): model = Team From 0b6fb7389f3fb438ee1aec6aa9b0d167432492a2 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 24 Nov 2021 16:20:20 -0600 Subject: [PATCH 03/10] Add link to home page for teamsnap events list --- benchcoach/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchcoach/views.py b/benchcoach/views.py index 1a7c97f..9d919d4 100644 --- a/benchcoach/views.py +++ b/benchcoach/views.py @@ -2,5 +2,5 @@ from django.http import HttpResponse from django.shortcuts import render def welcome(request): - pages = ['events list', 'teams list', 'venues list', 'players list'] + pages = ['events list', 'teams list', 'venues list', 'players list', 'teamsnap list events'] return render(request,'home.html',{'pages':pages}) \ No newline at end of file From 1a17e410b2b7ae6b26a57f12f23640e1c93cd407 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 24 Nov 2021 16:22:12 -0600 Subject: [PATCH 04/10] create a page for edit lineup based on teamsnap models --- teamsnap/templates/teamsnap/lineup.html | 165 ++++++++++++++++++ .../teamsnap/player-table-header.html | 8 + .../templates/teamsnap/player-table-row.html | 72 ++++++++ teamsnap/urls.py | 3 +- 4 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 teamsnap/templates/teamsnap/lineup.html create mode 100644 teamsnap/templates/teamsnap/player-table-header.html create mode 100644 teamsnap/templates/teamsnap/player-table-row.html diff --git a/teamsnap/templates/teamsnap/lineup.html b/teamsnap/templates/teamsnap/lineup.html new file mode 100644 index 0000000..5528f0f --- /dev/null +++ b/teamsnap/templates/teamsnap/lineup.html @@ -0,0 +1,165 @@ +{% extends 'base.html' %}{% block title %} {{ title }} {% endblock %}{% load crispy_forms_tags %}{% load static %} + +{% block content %} +
    +

    {{ event.formatted_title }}

    +
    +

    {{ event.start_date|date:"l, F j, Y g:i A" }}
    {{ event.location.name }}

    +
    +
    +
    +
    + {% csrf_token %} + {{ formset.management_form }} +
    + +
    +
    +
    Lineup
    +
    + + {% include 'teamsnap/player-table-header.html' %} + + {% for form in formset %} + {% if form.instance.sequence or form.instance.label == "P" %} + {% include 'teamsnap/player-table-row.html' with form=form available_display="none" sequence_display="table-cell" %} + {% endif %} + {% endfor %} + +
    +
    +
    +
    +
    + +
    +
    +
    Bench
    +
    + + {% include 'teamsnap/player-table-header.html' %} + + {% for form in formset %} + {% if not form.instance.member.is_non_player %} + {% if not form.instance.sequence and not form.instance.label == "P" %} + {% include 'teamsnap/player-table-row.html' with form=form available_display="table-cell" sequence_display="none" %} + {% endif %} + {% endif %} + {% endfor %} + +
    + +
    +
    +
    + +
    +
    +
    + + + +{% endblock %} \ No newline at end of file diff --git a/teamsnap/templates/teamsnap/player-table-header.html b/teamsnap/templates/teamsnap/player-table-header.html new file mode 100644 index 0000000..ccebfeb --- /dev/null +++ b/teamsnap/templates/teamsnap/player-table-header.html @@ -0,0 +1,8 @@ + + +{# #} +{# #} +{# Name#} +{# Pos#} + + \ No newline at end of file diff --git a/teamsnap/templates/teamsnap/player-table-row.html b/teamsnap/templates/teamsnap/player-table-row.html new file mode 100644 index 0000000..14f9b9c --- /dev/null +++ b/teamsnap/templates/teamsnap/player-table-row.html @@ -0,0 +1,72 @@ + + {{ form.id.as_hidden }} + {{ form.event.as_hidden }} + {{ form.sequence.as_hidden }} + {{ form.member.as_hidden }} + {{ form.teamsnap_id.as_hidden }} + + {% if form.availability.status_code == 2 %} + + {% elif form.availability.status_code == 1%} + + Maybe + {% elif form.availability.status_code == 0%} + + No + {% else %} + + Unknown + {% endif %} + + + {% if form.sequence.value %} + + {% elif form.sequence.value == 0 %} + + {% endif %} + + + {{ form.instance.member.first_name }} {{ form.instance.member.last_name }}  + #{{ form.instance.member.jersey_number }} +{#
    {{ form.statline }}#} + + + {{ form.label }} + + {# {{ form.instance.position }}#} + \ No newline at end of file diff --git a/teamsnap/urls.py b/teamsnap/urls.py index a8e65a1..fbc48f4 100644 --- a/teamsnap/urls.py +++ b/teamsnap/urls.py @@ -7,5 +7,6 @@ from . import views urlpatterns = [ path('events', views.EventsListView.as_view(), name="teamsnap list events"), - path('edit/event/', views.edit_event, name='teamsnap edit event') + path('edit/event/', views.edit_event, name='teamsnap edit event'), + path('edit/lineup/', views.edit_lineup, name='teamsnap edit lineup') ] \ No newline at end of file From debd1656e26ed2dd006ed02f45579a0e76150e65 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 24 Nov 2021 16:22:45 -0600 Subject: [PATCH 05/10] script for reseting teamsnap data. --- reload_teamsnap.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 reload_teamsnap.sh diff --git a/reload_teamsnap.sh b/reload_teamsnap.sh new file mode 100644 index 0000000..e5d85e2 --- /dev/null +++ b/reload_teamsnap.sh @@ -0,0 +1,8 @@ +#!/bin/bash +PROJECT_PATH="/Users/asc/PycharmProjects_Local/benchcoach" +PYTHON_PATH="$PROJECT_PATH/venv/bin/python" +FIXTURES="2021cmba" +MANAGE_PY_PATH="$PROJECT_PATH/manage.py" +bash -cl "$PYTHON_PATH $MANAGE_PY_PATH migrate teamsnap zero" +bash -cl "$PYTHON_PATH $MANAGE_PY_PATH migrate teamsnap" +bash -cl "$PYTHON_PATH $PROJECT_PATH/teamsnap/scripts/import_teamsnap.py" \ No newline at end of file From b3ce652b2ccb2d43ba5fc12752433935206d75e1 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 24 Nov 2021 16:23:04 -0600 Subject: [PATCH 06/10] prepare for statistics from teamsnap api --- teamsnap/teamsnap/api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/teamsnap/teamsnap/api.py b/teamsnap/teamsnap/api.py index 2386993..0d8d584 100644 --- a/teamsnap/teamsnap/api.py +++ b/teamsnap/teamsnap/api.py @@ -49,6 +49,12 @@ class Opponent (ApiObject): class EventLineupEntry (ApiObject): rel = "event_lineup_entries" +class Statistics (ApiObject): + rel = "statistics" + +class MemberStatistics (ApiObject): + rel = "member_statistics" + class TeamSnap(APIClient): base_url = 'https://api.teamsnap.com/v3' From 3bea6c5e621626ce82e5a2c151fb4f8e77c780e9 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 24 Nov 2021 16:23:25 -0600 Subject: [PATCH 07/10] add information to successpage (will now list errors) --- templates/success.html | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/templates/success.html b/templates/success.html index e83e9e8..ef2da08 100644 --- a/templates/success.html +++ b/templates/success.html @@ -5,13 +5,20 @@ - + - Success! Redirecting... - {% for error in errors %} - error - {% endfor %} + {% if errors %} + Errors... + {% for error in errors %} + error + {% endfor %} + + {% else %} + Success! + {% endif %} + Redirecting... + {% endblock %} \ No newline at end of file From 41e64a4ca939999924d1cd3daa7ff768e2594527 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 24 Nov 2021 16:23:51 -0600 Subject: [PATCH 08/10] add lineup entry (analogous to positioning) --- teamsnap/forms.py | 21 ++++ teamsnap/migrations/0011_lineupentry.py | 27 +++++ .../migrations/0012_auto_20211121_2010.py | 23 ++++ .../0013_remove_lineupentry_name.py | 17 +++ .../0014_alter_lineupentry_teamsnap_id.py | 18 +++ teamsnap/models.py | 26 ++++- teamsnap/views.py | 103 +++++++++++++++--- 7 files changed, 219 insertions(+), 16 deletions(-) create mode 100644 teamsnap/forms.py create mode 100644 teamsnap/migrations/0011_lineupentry.py create mode 100644 teamsnap/migrations/0012_auto_20211121_2010.py create mode 100644 teamsnap/migrations/0013_remove_lineupentry_name.py create mode 100644 teamsnap/migrations/0014_alter_lineupentry_teamsnap_id.py diff --git a/teamsnap/forms.py b/teamsnap/forms.py new file mode 100644 index 0000000..f1fd691 --- /dev/null +++ b/teamsnap/forms.py @@ -0,0 +1,21 @@ +from django import forms +from .models import LineupEntry +from events.models import Event +from players.models import Player +from django.forms import modelformset_factory, inlineformset_factory, BaseModelFormSet,formset_factory +from crispy_forms.helper import FormHelper, Layout + +class LineupEntryForm(forms.ModelForm): + availability = None + class Meta: + model = LineupEntry + widgets = { + 'label': forms.Select(attrs={'class': 'form-control form-control-sm'}) + } + exclude = () + +LineupEntryFormSet = modelformset_factory( + model=LineupEntry, + form=LineupEntryForm, + extra=0 +) diff --git a/teamsnap/migrations/0011_lineupentry.py b/teamsnap/migrations/0011_lineupentry.py new file mode 100644 index 0000000..da9e16b --- /dev/null +++ b/teamsnap/migrations/0011_lineupentry.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.6 on 2021-11-21 18:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0010_event_is_game'), + ] + + operations = [ + migrations.CreateModel( + name='LineupEntry', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.PositiveSmallIntegerField(blank=True, choices=[(11, 'EH'), (1, 'P'), (2, 'C'), (3, '1B'), (4, '2B'), (5, '3B'), (6, 'SS'), (7, 'LF'), (8, 'CF'), (9, 'RF'), (10, 'DH')], default=None, null=True)), + ('sequence', models.PositiveSmallIntegerField(default=0, null=True)), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teamsnap.event')), + ('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teamsnap.member')), + ], + options={ + 'unique_together': {('member', 'event')}, + }, + ), + ] diff --git a/teamsnap/migrations/0012_auto_20211121_2010.py b/teamsnap/migrations/0012_auto_20211121_2010.py new file mode 100644 index 0000000..2294c80 --- /dev/null +++ b/teamsnap/migrations/0012_auto_20211121_2010.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.6 on 2021-11-21 20:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0011_lineupentry'), + ] + + operations = [ + migrations.AddField( + model_name='lineupentry', + name='name', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='lineupentry', + name='teamsnap_id', + field=models.CharField(max_length=10, null=True, unique=True), + ), + ] diff --git a/teamsnap/migrations/0013_remove_lineupentry_name.py b/teamsnap/migrations/0013_remove_lineupentry_name.py new file mode 100644 index 0000000..763bba4 --- /dev/null +++ b/teamsnap/migrations/0013_remove_lineupentry_name.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.6 on 2021-11-21 20:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0012_auto_20211121_2010'), + ] + + operations = [ + migrations.RemoveField( + model_name='lineupentry', + name='name', + ), + ] diff --git a/teamsnap/migrations/0014_alter_lineupentry_teamsnap_id.py b/teamsnap/migrations/0014_alter_lineupentry_teamsnap_id.py new file mode 100644 index 0000000..214a198 --- /dev/null +++ b/teamsnap/migrations/0014_alter_lineupentry_teamsnap_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-21 20:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teamsnap', '0013_remove_lineupentry_name'), + ] + + operations = [ + migrations.AlterField( + model_name='lineupentry', + name='teamsnap_id', + field=models.CharField(blank=True, max_length=10, null=True, unique=True), + ), + ] diff --git a/teamsnap/models.py b/teamsnap/models.py index 45cb086..f1aa5ea 100644 --- a/teamsnap/models.py +++ b/teamsnap/models.py @@ -106,4 +106,28 @@ class Availability(TeamsnapBaseModel): return f"{self.member} - {self.event} ({self.teamsnap_id})" class Meta: - verbose_name_plural = "availabilities" \ No newline at end of file + verbose_name_plural = "availabilities" + +class LineupEntry(TeamsnapBaseModel): + name = None + teamsnap_id = models.CharField(max_length=10, unique=True, null=True, blank=True) + member = models.ForeignKey(Member, on_delete=models.CASCADE) + event = models.ForeignKey(Event, on_delete=models.CASCADE) + positions = [ + (11, 'EH'), + (1, 'P'), + (2, 'C'), + (3, '1B'), + (4, '2B'), + (5, '3B'), + (6, 'SS'), + (7, 'LF'), + (8, 'CF'), + (9, 'RF'), + (10,'DH') + ] + label = models.PositiveSmallIntegerField(choices=positions, default=None, null=True, blank=True) + sequence = models.PositiveSmallIntegerField(default=0, null=True, blank=True) + + class Meta: + unique_together = ('member', 'event',) diff --git a/teamsnap/views.py b/teamsnap/views.py index e4e3da4..9702667 100644 --- a/teamsnap/views.py +++ b/teamsnap/views.py @@ -1,9 +1,19 @@ from django.shortcuts import render, redirect # from .teamsnap.api import TeamSnap, Team, Event, Availability -from .models import User, Member, Team, Event, Location +from .models import User, Member, Team, Event, Location, LineupEntry from django.views.generic.list import ListView from lib.views import BenchcoachListView +from .forms import LineupEntryForm, LineupEntryFormSet +from django.forms.models import model_to_dict +from django.urls import reverse +from django.db.models import Case, When + +def queryset_from_ids(Model, id_list): + #https://stackoverflow.com/questions/4916851/django-get-a-queryset-from-array-of-ids-in-specific-order + preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(id_list)]) + queryset = Model.objects.filter(pk__in=id_list).order_by(preserved) + return queryset def edit_event(request, id): event = Event.objects.get(id = id) @@ -17,19 +27,82 @@ class EventsListView(BenchcoachListView): title_strf = '{item.formatted_title}' body_strf = "{item.start_date:%a, %b %-d, %-I:%M %p},\n{item.location.name}" - # def get_context_data(self): - # context = super().get_context_data() - # for item in context['items']: - # item['buttons'].append( - # { - # 'label': 'Edit Lineup', - # 'href': reverse('edit lineup', args=[item['id']]) - # } - # ) - # return context + def get_context_data(self): + context = super().get_context_data() + for item in context['items']: + item['buttons'].append( + { + 'label': 'Edit Lineup', + 'href': reverse('teamsnap edit lineup', args=[item['id']]) + } + ) + return context -class TeamListView(ListView): - model = Team +class TeamListView(BenchcoachListView): + Model = Team + edit_url = 'teamsnap edit team' + list_url = 'teamsnap list teams' + page_title = "TeamSnap Teams" -class LocationListView(ListView): - model = Location \ No newline at end of file +class LocationListView(BenchcoachListView): + Model = Location + edit_url = 'teamsnap edit location' + list_url = 'teamsnap list locations' + page_title = "TeamSnap Locations" + +def edit_lineup(request, event_id): + + if request.method == 'POST': + # create a form instance and populate it with data from the request: + formset = LineupEntryFormSet(request.POST) + for form in formset: + if form.is_valid(): + # process the data in form.cleaned_data as required + # ... + # redirect to a new URL: + + if isinstance(form.cleaned_data['id'], LineupEntry): + positioning_id = form.cleaned_data.pop('id').id #FIXME this is a workaround, not sure why it is necessary + positioning = LineupEntry.objects.filter(id=positioning_id) + positioning.update(**form.cleaned_data) + did_create = False + else: + positioning = LineupEntry.objects.create(**form.cleaned_data, event_id=event_id) + did_create = True + else: + pass + return render(request, 'success.html', {'call_back':'teamsnap edit lineup','id':event_id, 'errors':[error for error in formset.errors if error]}, status=200) + # return render(request, 'success.html', {'call_back':'schedule'}) + event = Event.objects.get(id=event_id) + members = Member.objects.filter(is_non_player=False).prefetch_related('availability_set', 'lineupentry_set') + # players_d.sort(key=lambda d: (-d['availability'].available, d['last_name'])) + + for member in members: + LineupEntry.objects.get_or_create(member_id=member.id, event_id=event_id) + + qs_starting_lineup = LineupEntry.objects.filter(event_id=event_id, sequence__isnull=False, sequence__gt=0).order_by('sequence') + qs_bench = LineupEntry.objects.filter(event_id=event_id, sequence=0).prefetch_related('member__availability_set').order_by('member__last_name') + + # This is all a compromise to get the sorting just the way I wanted. THERE'S GOT TO BE A BETTER WAY + ids_starting_lineup = [item.id for item in qs_starting_lineup] + ids_bench_available = [item.id for item in qs_bench + if item.member.availability_set.get(event_id=event_id).status_code == 1] + ids_bench_maybe = [item.id for item in qs_bench + if item.member.availability_set.get(event_id=event_id).status_code == 2] + ids_bench_no = [item.id for item in qs_bench + if item.member.availability_set.get(event_id=event_id).status_code == 0] + ids_bench_unknown = [item.id for item in qs_bench + if item.member.availability_set.get(event_id=event_id).status_code is None] + qset = queryset_from_ids(LineupEntry, ids_starting_lineup + ids_bench_available + ids_bench_maybe + ids_bench_no + ids_bench_unknown) + + formset = LineupEntryFormSet(queryset=qset) + + for f in formset: + if f.instance.member_id: + f.availability = f.instance.member.availability_set.get(event_id=event_id) + # f.statline = f.instance.member.statline_set.get() + + return render(request, 'teamsnap/lineup.html', {'title': 'Lineup', + 'event': event, + 'formset': formset, + }) \ No newline at end of file From 193fbef370a099987b3cbff1376c841397755bb1 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 24 Nov 2021 17:58:22 -0600 Subject: [PATCH 09/10] some experimentation with subs --- teamsnap/templates/teamsnap/lineup.html | 32 +++++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/teamsnap/templates/teamsnap/lineup.html b/teamsnap/templates/teamsnap/lineup.html index 5528f0f..5337426 100644 --- a/teamsnap/templates/teamsnap/lineup.html +++ b/teamsnap/templates/teamsnap/lineup.html @@ -2,7 +2,7 @@ {% block content %}
    -

    {{ event.formatted_title }}

    +

    <{{ event.formatted_title }}>

    {{ event.start_date|date:"l, F j, Y g:i A" }}
    {{ event.location.name }}

    @@ -12,10 +12,10 @@ {% csrf_token %} {{ formset.management_form }}
    - -
    +{# #} +
    -
    Lineup
    +
    Lineup
    {% include 'teamsnap/player-table-header.html' %} @@ -27,7 +27,19 @@ {% endfor %}
    -
    +
    +
    +
    +
    +
    Subs
    +
    + + {% include 'teamsnap/player-table-header.html' %} + + + +
    +
    @@ -79,6 +91,16 @@ form_element_sequence.value = parseInt(member_rows[i].dataset.sequence) } } + var lineup = new Sortable.create( + document.getElementById('planned-subs'), { + animation: 150, + ghostClass: "ghost", + {#handle: ".bars-move",#} + group: { + put: 'clone', + pull: true + } + }) var lineup = new Sortable.create( document.getElementById('lineup'), { animation: 150, From 1d893a1d973965da56d2017cd0d406f0ff22f7fd Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 10 Dec 2021 10:18:48 -0600 Subject: [PATCH 10/10] consolidated player-table to consolidate, had to move the filtering to the context instead of the template (probably the right way to do it anyway) also switched from using display to using class to hide cells (also probably the more right/consistent way to do this) --- teamsnap/templates/teamsnap/lineup.html | 60 ++++--------- .../teamsnap/player-table-header.html | 8 -- .../templates/teamsnap/player-table-row.html | 72 ---------------- teamsnap/templates/teamsnap/player-table.html | 86 +++++++++++++++++++ teamsnap/views.py | 8 +- 5 files changed, 108 insertions(+), 126 deletions(-) delete mode 100644 teamsnap/templates/teamsnap/player-table-header.html delete mode 100644 teamsnap/templates/teamsnap/player-table-row.html create mode 100644 teamsnap/templates/teamsnap/player-table.html diff --git a/teamsnap/templates/teamsnap/lineup.html b/teamsnap/templates/teamsnap/lineup.html index 5337426..14d0dd1 100644 --- a/teamsnap/templates/teamsnap/lineup.html +++ b/teamsnap/templates/teamsnap/lineup.html @@ -17,28 +17,14 @@
    Lineup
    - - {% include 'teamsnap/player-table-header.html' %} - - {% for form in formset %} - {% if form.instance.sequence or form.instance.label == "P" %} - {% include 'teamsnap/player-table-row.html' with form=form available_display="none" sequence_display="table-cell" %} - {% endif %} - {% endfor %} - -
    + {% include 'teamsnap/player-table.html' with table_id="lineup" formset=formset_lineup available_class="d-none"%}
    -
    Subs
    +
    DH'd
    - - {% include 'teamsnap/player-table-header.html' %} - - - -
    + {% include 'teamsnap/player-table.html' with table_id="dhd" formset=formset_dhd available_class="d-none" sequence_class="d-none"%}
    @@ -48,19 +34,7 @@
    Bench
    - - {% include 'teamsnap/player-table-header.html' %} - - {% for form in formset %} - {% if not form.instance.member.is_non_player %} - {% if not form.instance.sequence and not form.instance.label == "P" %} - {% include 'teamsnap/player-table-row.html' with form=form available_display="table-cell" sequence_display="none" %} - {% endif %} - {% endif %} - {% endfor %} - -
    - + {% include 'teamsnap/player-table.html' with table_id="bench" formset=formset_bench sequence_class="d-none"%}
    @@ -68,7 +42,7 @@
    - +