From 9445940df27d05c56f0a71dd22743cca948e6340 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 20 Nov 2021 18:53:21 -0600 Subject: [PATCH] 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