From 9b26bbe97382e6d6f14c20879869ac2ac6f96272 Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 20 Jun 2022 09:35:59 -0500 Subject: [PATCH 1/5] gamechanger updates (added events, teams). not used yet. --- gamechanger/admin.py | 3 +- gamechanger/forms.py | 6 +- .../migrations/0006_auto_20220615_1330.py | 37 ++++++++++++ .../migrations/0007_remove_team_season_id.py | 17 ++++++ .../0008_rename_name_slug_team_slug.py | 18 ++++++ gamechanger/models.py | 9 ++- gamechanger/utils/gamechanger.py | 52 +++++++++++----- gamechanger/views.py | 60 +++++++++++++++---- 8 files changed, 169 insertions(+), 33 deletions(-) create mode 100644 gamechanger/migrations/0006_auto_20220615_1330.py create mode 100644 gamechanger/migrations/0007_remove_team_season_id.py create mode 100644 gamechanger/migrations/0008_rename_name_slug_team_slug.py diff --git a/gamechanger/admin.py b/gamechanger/admin.py index 30bcba8..20f80cb 100644 --- a/gamechanger/admin.py +++ b/gamechanger/admin.py @@ -1,8 +1,9 @@ from django.contrib import admin -from .models import Account, Player, Preferences +from .models import Account, Player, Preferences, Team # Register your models here. admin.site.register(Account) admin.site.register(Preferences) admin.site.register(Player) +admin.site.register(Team) diff --git a/gamechanger/forms.py b/gamechanger/forms.py index 627f939..0c93568 100644 --- a/gamechanger/forms.py +++ b/gamechanger/forms.py @@ -5,14 +5,14 @@ from .models import Account, Player, Preferences class PreferencesForm(ModelForm): + season_id = "" + class Meta: model = Preferences - fields = ["user", "season_id", "team_id"] + fields = ["user", "managed_team"] widgets = { "user": forms.HiddenInput(), - "managed_team_id": forms.TextInput(), } - labels = {"managed_team_id": "Selected Team"} class AccountForm(ModelForm): diff --git a/gamechanger/migrations/0006_auto_20220615_1330.py b/gamechanger/migrations/0006_auto_20220615_1330.py new file mode 100644 index 0000000..638f996 --- /dev/null +++ b/gamechanger/migrations/0006_auto_20220615_1330.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.13 on 2022-06-15 18:30 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gamechanger', '0005_alter_preferences_options'), + ] + + operations = [ + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.CharField(max_length=30, primary_key=True, serialize=False)), + ('season_id', models.CharField(max_length=30)), + ('name_slug', models.CharField(max_length=30)), + ('season_slug', models.CharField(max_length=30)), + ], + ), + migrations.RemoveField( + model_name='preferences', + name='season_id', + ), + migrations.RemoveField( + model_name='preferences', + name='team_id', + ), + migrations.AddField( + model_name='preferences', + name='managed_team', + field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.CASCADE, to='gamechanger.team'), + preserve_default=False, + ), + ] diff --git a/gamechanger/migrations/0007_remove_team_season_id.py b/gamechanger/migrations/0007_remove_team_season_id.py new file mode 100644 index 0000000..566dbbe --- /dev/null +++ b/gamechanger/migrations/0007_remove_team_season_id.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.13 on 2022-06-15 19:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gamechanger', '0006_auto_20220615_1330'), + ] + + operations = [ + migrations.RemoveField( + model_name='team', + name='season_id', + ), + ] diff --git a/gamechanger/migrations/0008_rename_name_slug_team_slug.py b/gamechanger/migrations/0008_rename_name_slug_team_slug.py new file mode 100644 index 0000000..a89afdc --- /dev/null +++ b/gamechanger/migrations/0008_rename_name_slug_team_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2022-06-15 21:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gamechanger', '0007_remove_team_season_id'), + ] + + operations = [ + migrations.RenameField( + model_name='team', + old_name='name_slug', + new_name='slug', + ), + ] diff --git a/gamechanger/models.py b/gamechanger/models.py index 363a83c..d1c82c5 100644 --- a/gamechanger/models.py +++ b/gamechanger/models.py @@ -14,12 +14,17 @@ class Account(models.Model): password = CharField(max_length=255) +class Team(models.Model): + id = models.CharField(primary_key=True, max_length=30) + slug = CharField(max_length=30) + season_slug = CharField(max_length=30) + + class Preferences(models.Model): user = models.OneToOneField( User, on_delete=models.CASCADE, related_name="gamechanger_preferences" ) - season_id = CharField(max_length=255) - team_id = CharField(max_length=255) + managed_team = models.OneToOneField(Team, on_delete=models.CASCADE) class Meta: verbose_name_plural = "preferences" diff --git a/gamechanger/utils/gamechanger.py b/gamechanger/utils/gamechanger.py index 64cfab3..6825ea6 100644 --- a/gamechanger/utils/gamechanger.py +++ b/gamechanger/utils/gamechanger.py @@ -7,7 +7,7 @@ import pytz import requests from bs4 import BeautifulSoup -url = "https://gc.com/t/{season_id}/{team_id}/{page}" +url = "https://gc.com/t/{season_id}/{team_slug}-{team_id}/{page}" def get_authenticated_session(request): @@ -35,12 +35,16 @@ def get_authenticated_session(request): def submit_lineup(request, lineup): authenticated_session = get_authenticated_session(request) - season_id = request.user.gamechanger_preferences.season_id - team_id = request.user.gamechanger_preferences.team_id + season_id = request.user.gamechanger_preferences.managed_team.season_slug + team_slug = request.user.gamechanger_preferences.managed_team.slug + team_id = request.user.gamechanger_preferences.managed_team.id authenticated_session.headers.update( { "referer": url.format( - season_id=season_id, team_id=team_id, page="lineup_edit" + season_id=season_id, + team_slug=team_slug, + team_id=team_id, + page="lineup_edit", ), "x-csrftoken": authenticated_session.cookies.get("csrftoken"), "Content-Type": "application/x-www-form-urlencoded;", @@ -48,12 +52,10 @@ def submit_lineup(request, lineup): ) r = authenticated_session.post( cookies=authenticated_session.cookies, - url="https://gc.com/do-save-lineup/{team_id}".format( - team_id=team_id.split("-").pop() - ), + url=f"https://gc.com/do-save-lineup/{team_id}", json={"lineup": lineup}, ) - if r.status_code == 200: + if r.status_code == 20 and r.content == b"OK": return r else: raise requests.exceptions.RequestException( @@ -61,8 +63,10 @@ def submit_lineup(request, lineup): ) -def scrape_page(season_id, team_id, page): - r = requests.get(url.format(season_id=season_id, team_id=team_id, page=page)) +def scrape_page(season_id, team_id, team_slug, page): + r = requests.get( + url.format(season_id=season_id, team_id=team_id, team_slug=team_slug, page=page) + ) initialize_page_json = re.search( r'page.initialize\(\$.parseJSON\("(.*?)"\)', r.content.decode("unicode_escape") ) @@ -160,14 +164,34 @@ def stream(): def stats(request): authenticated_session = get_authenticated_session(request) - season_id = request.user.gamechanger_preferences.season_id - team_id = request.user.gamechanger_preferences.team_id + season_id = request.user.gamechanger_preferences.managed_team.season_slug + team_id = request.user.gamechanger_preferences.managed_team.id + team_slug = request.user.gamechanger_preferences.managed_team.slug page = "stats/batting/Qualified/standard/csv" + authenticated_session.headers.update( + { + "referer": url.format( + season_id=season_id, team_id=team_id, team_slug=team_slug, page="stats" + ), + "x-csrftoken": authenticated_session.cookies.get("csrftoken"), + } + ) r = authenticated_session.get( - url.format(season_id=season_id, team_id=team_id, page=page) + cookies=authenticated_session.cookies, + url=url.format( + season_id=season_id, team_id=team_id, team_slug=team_slug, page=page + ), ) - roster = scrape_page(season_id, team_id, "roster") + if ( + r.status_code != 200 + or "Please sign in or join to continue." in r.content.decode("utf-8") + ): + raise Exception("Stats fetch failed.") + + roster = scrape_page( + season_id=season_id, team_id=team_id, team_slug=team_slug, page="roster" + ) id_lookup = { (p.get("fname"), p.get("lname")): p.get("player_id") for p in roster["roster"] } diff --git a/gamechanger/views.py b/gamechanger/views.py index 3c56e97..d2e9c1b 100644 --- a/gamechanger/views.py +++ b/gamechanger/views.py @@ -1,3 +1,4 @@ +from django import forms from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseServerError from django.shortcuts import render from django.views.generic.edit import FormView @@ -5,7 +6,7 @@ from django.views.generic.edit import FormView from teamsnap.views import get_teamsnap_client from .forms import AccountForm, PlayerFormSet, PreferencesForm -from .models import Account, Player, Preferences +from .models import Account, Player, Preferences, Team from .utils import gamechanger @@ -30,10 +31,15 @@ class PreferencesFormView(FormView): def form_valid(self, form): # This method is called when valid form data has been POSTed. # It should return an HttpResponse. - if form.data["user"] == str(self.request.user.id): + if form.cleaned_data["user"].username == str( + self.request.user.gamechanger_account.user + ): form.save() return super().form_valid(form) + def form_invalid(self, form): + pass + def get_initial(self): """ Returns the initial data to use for forms on this view. @@ -51,11 +57,32 @@ class PreferencesFormView(FormView): """ try: - contact = Preferences.objects.get(user=self.request.user) - form = PreferencesForm(instance=contact, **self.get_form_kwargs()) + preferences = Preferences.objects.get(user=self.request.user) + form = PreferencesForm(instance=preferences, **self.get_form_kwargs()) + except Preferences.DoesNotExist: form = super().get_form(self.form_class) + gc_session = gamechanger.get_authenticated_session(self.request) + teams = gamechanger.get_teams(gc_session) + + team_instances = [] + choices = [] + for team in teams: + instance, _ = Team.objects.get_or_create( + id=team["id"], + slug="-".join(team["team_slug"].split("-")[:-1]), + season_slug=team["season_slug"], + ) + team_instances.append(instance) + choices.append((team["id"], f"{team['name']} ({team['season']})")) + + form.fields["managed_team"].widget = forms.Select( + choices=choices, attrs={"class": "form-control"} + ) + # form.fields["managed_team"].choices = [choice[0] for choice in choices] + # form.fields["managed_team"].widget.choices = choices + return form @@ -97,8 +124,8 @@ class AccountFormView(FormView): def roster(request): - season_id = request.user.gamechanger_preferences.season_id - team_id = request.user.gamechanger_preferences.team_id + season_id = request.user.gamechanger_preferences.managed_team.season_slug + team_id = request.user.gamechanger_preferences.id page = "roster" d = gamechanger.scrape_page(season_id, team_id, page) roster = d["roster"] @@ -110,8 +137,9 @@ def roster_import(request): from pyteamsnap.api import Member client = get_teamsnap_client(request) - season_id = request.user.gamechanger_preferences.season_id - team_id = request.user.gamechanger_preferences.team_id + season_id = request.user.gamechanger_preferences.managed_team.season_slug + team_slug = request.user.gamechanger_preferences.managed_team.slug + team_id = request.user.gamechanger_preferences.managed_team.id teamsnap_team_id = request.user.teamsnap_preferences.managed_team_id teamsnap_members = { f"{member.data['first_name']} {member.data['last_name']}": member @@ -120,7 +148,7 @@ def roster_import(request): page = "roster" - d = gamechanger.scrape_page(season_id, team_id, page) + d = gamechanger.scrape_page(season_id, team_id, team_slug, page) roster = d["roster"] initial = [ { @@ -128,12 +156,18 @@ def roster_import(request): "fname": player["fname"], "lname": player["lname"], "teamsnap_name": "{first_name} {last_name}".format( - **teamsnap_members[f"{player['fname']} {player['lname']}"].data + **getattr( + teamsnap_members.get(f"{player['fname']} {player['lname']}"), + "data", + {"first_name": "", "last_name": ""}, + ) ), "id": player.get("player_id"), - "teamsnap_member_id": teamsnap_members[ - f"{player['fname']} {player['lname']}" - ].data["id"], + "teamsnap_member_id": getattr( + teamsnap_members.get(f"{player['fname']} {player['lname']}"), + "data", + {"id": None}, + )["id"], } for player in roster ] From cf28a5f1334452d4c91ac96234b1bbd0eb262b5b Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 23 Jun 2022 07:24:07 -0500 Subject: [PATCH 2/5] incorporate new pyteamsnap, incorporate loading from sessions before creating new client. --- config/settings/base.py | 1 + teamsnap/dashboard/views.py | 2 +- teamsnap/lineup/views.py | 9 ++++----- teamsnap/utils/__init__.py | 32 ++++++++++++++++++++++---------- teamsnap/views.py | 7 ++++--- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/config/settings/base.py b/config/settings/base.py index e79480a..16a8608 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -281,3 +281,4 @@ INSTALLED_APPS += [ "teamsnap.dashboard", ] SOCIALACCOUNT_PROVIDERS = {"teamsnap": {"SCOPE": ["read", "write"]}} +SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer" diff --git a/teamsnap/dashboard/views.py b/teamsnap/dashboard/views.py index 13163f1..bcb9d55 100644 --- a/teamsnap/dashboard/views.py +++ b/teamsnap/dashboard/views.py @@ -25,7 +25,7 @@ def dashboard(request, team_id=None): team_id=request.user.teamsnap_preferences.managed_team_id, ) - from pyteamsnap.api import AvailabilitySummary, Event + from pyteamsnap.objects import AvailabilitySummary, Event client = get_teamsnap_client(request) ts_events = Event.search(client, team_id=team_id) diff --git a/teamsnap/lineup/views.py b/teamsnap/lineup/views.py index ae5a8a5..a1bd75b 100644 --- a/teamsnap/lineup/views.py +++ b/teamsnap/lineup/views.py @@ -17,7 +17,7 @@ def teamsnap_event_redirect(request, event_id, team_id): def edit_lineup(request, event_ids, team_id): import re - from pyteamsnap.api import ( + from pyteamsnap.objects import ( Availability, AvailabilitySummary, Event, @@ -25,7 +25,6 @@ def edit_lineup(request, event_ids, team_id): EventLineupEntry, Member, ) - from teamsnap.forms import LineupEntryFormset client = get_teamsnap_client(request) @@ -200,8 +199,7 @@ def edit_lineup(request, event_ids, team_id): def submit_lineup(request, team_id, event_id): - from pyteamsnap.api import Event, EventLineup, EventLineupEntry - + from pyteamsnap.objects import Event, EventLineup, EventLineupEntry from teamsnap.forms import LineupEntryFormset client = get_teamsnap_client(request) @@ -260,7 +258,8 @@ def multi_lineup_choose(request, team_id=None): team_id=request.user.teamsnap_preferences.managed_team_id, ) from django.forms import formset_factory - from pyteamsnap.api import Event + + from pyteamsnap.objects import Event from .forms import EventChooseForm diff --git a/teamsnap/utils/__init__.py b/teamsnap/utils/__init__.py index 6b31ac6..01add0d 100644 --- a/teamsnap/utils/__init__.py +++ b/teamsnap/utils/__init__.py @@ -1,14 +1,26 @@ -import pyteamsnap +import logging + +from pyteamsnap.client import TeamSnap + +# This retrieves a Python logging instance (or creates it) +logger = logging.getLogger(__name__) def get_teamsnap_client(request): - request.user.socialaccount_set.filter(provider="teamsnap").first() - current_teamsnap_user = request.user.socialaccount_set.filter( - provider="teamsnap" - ).first() + client = request.session.get("teamsnap_client") + if client: + logger.info("TeamSnap client found saved in session, loading.") + return client + elif not client: + logger.info("No TeamSnap client saved in session, getting one.") + request.user.socialaccount_set.filter(provider="teamsnap").first() + current_teamsnap_user = request.user.socialaccount_set.filter( + provider="teamsnap" + ).first() - ts_token = ( - current_teamsnap_user.socialtoken_set.order_by("-expires_at").first().token - ) - - return pyteamsnap.api.TeamSnap(token=ts_token) + ts_token = ( + current_teamsnap_user.socialtoken_set.order_by("-expires_at").first().token + ) + client = TeamSnap(token=ts_token) + request.session["teamsnap_client"] = client + return client diff --git a/teamsnap/views.py b/teamsnap/views.py index 8e97d43..c5ed20d 100644 --- a/teamsnap/views.py +++ b/teamsnap/views.py @@ -116,7 +116,7 @@ def schedule_view(request, team_id=None): client = get_teamsnap_client(request) no_past = bool(request.GET.get("no_past", 0)) games_only = bool(request.GET.get("games_only", 0)) - from pyteamsnap.api import Event + from pyteamsnap.objects import Event ts_events = Event.search(client, team_id=team_id) if no_past: @@ -143,7 +143,7 @@ def view_event(request, event_id, team_id=None): "teamsnap_event", team_id=request.user.teamsnap_preferences.managed_team_id ) - from pyteamsnap.api import ( + from pyteamsnap.objects import ( AvailabilitySummary, Event, EventLineup, @@ -183,7 +183,8 @@ def view_event(request, event_id, team_id=None): def multi_lineup_choose(request, team_id): from django.forms import formset_factory - from pyteamsnap.api import Event + + from pyteamsnap.objects import Event from .forms import EventChooseForm From c31cd1cce51b93374280b491487508242fb33f39 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 23 Jun 2022 07:31:37 -0500 Subject: [PATCH 3/5] incorporate new module gamescrapyr, incorporate loading from sessions before creating new client. --- .idea/benchcoach.iml | 2 + .idea/modules.xml | 2 + .idea/vcs.xml | 3 +- gamechanger/utils/gamechanger.py | 256 +++---------------------------- gamechanger/views.py | 31 ++-- 5 files changed, 46 insertions(+), 248 deletions(-) diff --git a/.idea/benchcoach.iml b/.idea/benchcoach.iml index 018170f..e73d316 100644 --- a/.idea/benchcoach.iml +++ b/.idea/benchcoach.iml @@ -21,6 +21,8 @@ + +