Merge branch 'v2' into gamecard

# Conflicts:
#	benchcoach/static/css/gamecard.css
#	benchcoach/static/fonts/dinpro/dinpro.css
#	benchcoach/static/fonts/m+1m/m+1m.css
#	benchcoach/static/fonts/m+1m/mplus-1m-bold-webfont.woff
#	benchcoach/static/fonts/m+1m/mplus-1m-light-webfont.woff
#	benchcoach/static/fonts/m+1m/mplus-1m-medium-webfont.woff
#	benchcoach/static/fonts/m+1m/mplus-1m-regular-webfont.woff
#	benchcoach/static/fonts/m+1m/mplus-1m-thin-webfont.woff
#	benchcoach/static/fonts/refrigerator/refigerator.css
#	benchcoach/static/fonts/vera/Vera-Bold-Italic-webfont.woff
#	benchcoach/static/fonts/vera/Vera-Bold-webfont.woff
#	benchcoach/static/fonts/vera/Vera-Italic-webfont.woff
#	benchcoach/static/fonts/vera/Vera-webfont.woff
#	benchcoach/static/fonts/vera/VeraMono-Bold-Italic-webfont.woff
#	benchcoach/static/fonts/vera/VeraMono-Bold-webfont.woff
#	benchcoach/static/fonts/vera/VeraMono-Italic-webfont.woff
#	benchcoach/static/fonts/vera/VeraMono-webfont.woff
#	benchcoach/static/fonts/vera/bitstreamvera.css
#	benchcoach/static/fonts/verdana/VerdanaPro-Black.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-BlackItalic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-Bold.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-BoldItalic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondBlack.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondBlackItalic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondBold.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondBoldItalic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondItalic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondLight.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondLightItalic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondRegular.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondSemiBold.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-CondSemiBoldItalic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-Italic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-Light.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-LightItalic.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-Regular.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-SemiBold.ttf
#	benchcoach/static/fonts/verdana/VerdanaPro-SemiBoldItalic.ttf
#	benchcoach/static/fonts/verdana/verdanapro.css
#	benchcoachproject/static/css/base.css
#	teamsnap/templates/lineup/gamecard.html
#	teamsnap/urls.py
#	teamsnap/views.py
This commit is contained in:
2022-06-07 18:31:38 -05:00
533 changed files with 5402 additions and 28338 deletions

View File

@@ -1,11 +1,6 @@
from django.contrib import admin
from .models import User, Team, Location, Event, Member, Availability, Opponent
from .models import Preferences
# Register your models here.
admin.site.register(User)
admin.site.register(Team)
admin.site.register(Event)
admin.site.register(Location)
admin.site.register(Member)
admin.site.register(Availability)
admin.site.register(Opponent)
admin.site.register(Preferences)

View File

@@ -2,5 +2,5 @@ from django.apps import AppConfig
class TeamsnapConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'teamsnap'
default_auto_field = "django.db.models.BigAutoField"
name = "teamsnap"

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +1,21 @@
from django import forms
from .models import Team, Location, Opponent, Event, Member
from django.forms import modelformset_factory, formset_factory, inlineformset_factory
from django.forms import ModelForm, formset_factory
select_kwargs = {
'attrs':{'class': 'form-control form-control-sm'}
}
from .models import Preferences
text_input_kwargs = {
'attrs':{"readonly": "readonly", 'class':'form-control form-control-sm'}
}
class MemberForm(forms.ModelForm):
class PreferencesForm(ModelForm):
class Meta:
model = Member
fields = ('first_name', 'last_name', 'benchcoach_object')
labels = {
'benchcoach_object': 'BenchCoach Link',
}
model = Preferences
fields = ["user", "managed_team_id"]
widgets = {
"benchcoach_object": forms.Select(**select_kwargs),
"first_name": forms.TextInput(**text_input_kwargs),
"last_name": forms.TextInput(**text_input_kwargs),
"user": forms.HiddenInput(),
"managed_team_id": forms.Select(
choices=(), attrs={"class": "form-control"}
),
}
labels = {"managed_team_id": "Selected Team"}
class EventForm(forms.ModelForm):
class Meta:
model = Event
fields = ('formatted_title', 'start_date', 'benchcoach_object')
labels ={
'formatted_title':"Title",
'benchcoach_object':'BenchCoach Link',
'start_date':'Date/Time'
}
widgets = {
"benchcoach_object": forms.Select(**select_kwargs),
"formatted_title":forms.TextInput(**text_input_kwargs),
"start_date": forms.DateTimeInput(**text_input_kwargs)
}
class TeamForm(forms.ModelForm):
class Meta:
model = Team
fields = ('name', 'benchcoach_object')
labels ={
'benchcoach_object':'BenchCoach Link',
}
widgets = {
"name":forms.TextInput(**text_input_kwargs),
"benchcoach_object": forms.Select(**select_kwargs)
}
class OpponentForm(forms.ModelForm):
class Meta:
model = Opponent
fields = ('name', 'benchcoach_object')
labels ={
'benchcoach_object':'BenchCoach Link',
}
widgets = {
"name":forms.TextInput(**text_input_kwargs),
"benchcoach_object": forms.Select(**select_kwargs)
}
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = ('name', 'benchcoach_object')
labels ={
'benchcoach_object':'BenchCoach Link',
}
widgets = {
"name":forms.TextInput(**text_input_kwargs),
"benchcoach_object": forms.Select(**select_kwargs)
}
class LineupEntryForm(forms.Form):
member = None
@@ -85,35 +28,27 @@ class LineupEntryForm(forms.Form):
member_id = forms.Field()
position_only = forms.BooleanField(initial=False, required=False)
sequence = forms.IntegerField(required=False)
label = forms.ChoiceField(required=False, choices=[
("", "--"),
("P","P"),
("C","C"),
("1B","1B"),
("2B", "2B"),
("3B", "3B"),
("SS", "SS"),
('LF','LF'),
('CF','CF'),
('RF','RF'),
('DH','DH'),
('DR','DR'),
('EH','EH')
],
widget=forms.Select(
attrs = {'onchange' : "colorPositions();"}
)
)
class EventChooseForm(forms.Form):
event_id = forms.ChoiceField()
# checked = forms.BooleanField(required=False)
# def __init__(self, events, *args, **kwargs):
# super(EventChooseForm, self).__init__(*args, **kwargs)
# self.fields['foo'].choices = [e.data['id'] for e in events]
LineupEntryFormset = formset_factory(LineupEntryForm, can_delete=True, can_order=True, extra=0)
label = forms.ChoiceField(
required=False,
choices=[
("", "--"),
("P", "P"),
("C", "C"),
("1B", "1B"),
("2B", "2B"),
("3B", "3B"),
("SS", "SS"),
("LF", "LF"),
("CF", "CF"),
("RF", "RF"),
("DH", "DH"),
("DR", "DR"),
("EH", "EH"),
],
widget=forms.Select(attrs={"onchange": "colorPositions();"}),
)
LineupEntryFormset = formset_factory(
LineupEntryForm, can_delete=True, can_order=True, extra=0
)

View File

@@ -1,5 +1,6 @@
# Generated by Django 3.2.6 on 2021-12-17 21:35
# Generated by Django 3.2.13 on 2022-06-02 13:20
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
@@ -9,134 +10,16 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('benchcoach', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Team',
name='Preferences',
fields=[
('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=50, null=True)),
('created_at', models.DateTimeField(null=True)),
('updated_at', models.DateTimeField(null=True)),
('benchcoach_object', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnapteam', to='benchcoach.team')),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('managed_team_id', models.IntegerField()),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='User',
fields=[
('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(null=True)),
('updated_at', models.DateTimeField(null=True)),
('first_name', models.CharField(max_length=50, null=True)),
('last_name', models.CharField(max_length=50, null=True)),
('email', models.EmailField(max_length=254, null=True)),
('managed_teams', models.ManyToManyField(to='teamsnap.Team')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Opponent',
fields=[
('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=50, null=True)),
('created_at', models.DateTimeField(null=True)),
('updated_at', models.DateTimeField(null=True)),
('benchcoach_object', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='benchcoach.team')),
('managed_by_team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Member',
fields=[
('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(null=True)),
('updated_at', models.DateTimeField(null=True)),
('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()),
('benchcoach_object', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='benchcoach.player')),
('managed_by_team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Location',
fields=[
('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=50, null=True)),
('created_at', models.DateTimeField(null=True)),
('updated_at', models.DateTimeField(null=True)),
('benchcoach_object', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='benchcoach.venue')),
('managed_by_team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Event',
fields=[
('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(null=True)),
('updated_at', models.DateTimeField(null=True)),
('label', models.CharField(max_length=50, null=True)),
('start_date', models.DateTimeField(null=True)),
('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()),
('benchcoach_object', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_event', to='benchcoach.event')),
('location', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.location')),
('managed_by_team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')),
('opponent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opponent', to='teamsnap.opponent')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Availability',
fields=[
('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(null=True)),
('updated_at', models.DateTimeField(null=True)),
('status_code', models.SmallIntegerField(choices=[(1, 'Yes'), (0, 'No'), (2, 'Maybe'), (None, 'Unknown')], default=None, null=True)),
('benchcoach_object', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='benchcoach.availability')),
('event', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.event')),
('managed_by_team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')),
('member', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.member')),
],
options={
'verbose_name_plural': 'availabilities',
},
),
migrations.CreateModel(
name='LineupEntry',
fields=[
('id', models.CharField(max_length=50, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(null=True)),
('updated_at', models.DateTimeField(null=True)),
('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(blank=True, default=0, null=True)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teamsnap.event')),
('managed_by_team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')),
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teamsnap.member')),
],
options={
'unique_together': {('member', 'event')},
},
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-18 23:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('teamsnap', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='event',
name='game_type',
field=models.CharField(max_length=50, null=True),
),
]

View File

@@ -1,108 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-20 02:58
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('benchcoach', '0002_alter_player_jersey_number'),
('teamsnap', '0002_event_game_type'),
]
operations = [
migrations.RemoveField(
model_name='availability',
name='managed_by_team',
),
migrations.RemoveField(
model_name='event',
name='managed_by_team',
),
migrations.RemoveField(
model_name='location',
name='managed_by_team',
),
migrations.RemoveField(
model_name='member',
name='managed_by_team',
),
migrations.RemoveField(
model_name='opponent',
name='managed_by_team',
),
migrations.AddField(
model_name='availability',
name='team',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team', verbose_name='managed by team'),
),
migrations.AddField(
model_name='event',
name='team',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team', verbose_name='managed by team'),
),
migrations.AddField(
model_name='lineupentry',
name='benchcoach_object',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_lineupentry', to='benchcoach.positioning'),
),
migrations.AddField(
model_name='lineupentry',
name='team',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team', verbose_name='managed by team'),
),
migrations.AddField(
model_name='location',
name='team',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team', verbose_name='managed by team'),
),
migrations.AddField(
model_name='member',
name='team',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team', verbose_name='managed by team'),
),
migrations.AddField(
model_name='opponent',
name='team',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team', verbose_name='managed by team'),
),
migrations.AlterField(
model_name='availability',
name='benchcoach_object',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_availability', to='benchcoach.availability'),
),
migrations.AlterField(
model_name='event',
name='benchcoach_object',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_event', to='benchcoach.event'),
),
migrations.AlterField(
model_name='location',
name='benchcoach_object',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_location', to='benchcoach.venue'),
),
migrations.AlterField(
model_name='member',
name='benchcoach_object',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_member', to='benchcoach.player'),
),
migrations.AlterField(
model_name='opponent',
name='benchcoach_object',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_opponent', to='benchcoach.team'),
),
migrations.AlterField(
model_name='team',
name='benchcoach_object',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_team', to='benchcoach.team'),
),
migrations.AlterUniqueTogether(
name='lineupentry',
unique_together=set(),
),
migrations.RemoveField(
model_name='lineupentry',
name='managed_by_team',
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-22 15:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('teamsnap', '0003_auto_20211219_2058'),
]
operations = [
migrations.AlterField(
model_name='event',
name='is_game',
field=models.BooleanField(null=True),
),
migrations.AlterField(
model_name='member',
name='is_non_player',
field=models.BooleanField(null=True),
),
]

View File

@@ -1,50 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-24 16:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('benchcoach', '0002_alter_player_jersey_number'),
('teamsnap', '0004_auto_20211222_0957'),
]
operations = [
migrations.AlterField(
model_name='availability',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_availability', to='benchcoach.availability'),
),
migrations.AlterField(
model_name='event',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_event', to='benchcoach.event'),
),
migrations.AlterField(
model_name='lineupentry',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_lineupentry', to='benchcoach.positioning'),
),
migrations.AlterField(
model_name='location',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_location', to='benchcoach.venue'),
),
migrations.AlterField(
model_name='member',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_member', to='benchcoach.player'),
),
migrations.AlterField(
model_name='opponent',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_opponent', to='benchcoach.team'),
),
migrations.AlterField(
model_name='team',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_team', to='benchcoach.team'),
),
]

View File

@@ -1,50 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-29 01:06
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('benchcoach', '0002_alter_player_jersey_number'),
('teamsnap', '0004_auto_20211222_0957'),
]
operations = [
migrations.AlterField(
model_name='availability',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_object+', to='benchcoach.availability'),
),
migrations.AlterField(
model_name='event',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_object+', to='benchcoach.event'),
),
migrations.AlterField(
model_name='lineupentry',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_object+', to='benchcoach.positioning'),
),
migrations.AlterField(
model_name='location',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_object+', to='benchcoach.venue'),
),
migrations.AlterField(
model_name='member',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_object+', to='benchcoach.player'),
),
migrations.AlterField(
model_name='opponent',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_object+', to='benchcoach.team'),
),
migrations.AlterField(
model_name='team',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_object+', to='benchcoach.team'),
),
]

View File

@@ -1,50 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-29 01:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('benchcoach', '0002_alter_player_jersey_number'),
('teamsnap', '0005_auto_20211228_1906'),
]
operations = [
migrations.AlterField(
model_name='availability',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_availability', to='benchcoach.availability'),
),
migrations.AlterField(
model_name='event',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_event', to='benchcoach.event'),
),
migrations.AlterField(
model_name='lineupentry',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_lineupentry', to='benchcoach.positioning'),
),
migrations.AlterField(
model_name='location',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_location', to='benchcoach.venue'),
),
migrations.AlterField(
model_name='member',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_member', to='benchcoach.player'),
),
migrations.AlterField(
model_name='opponent',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_opponent', to='benchcoach.team'),
),
migrations.AlterField(
model_name='team',
name='benchcoach_object',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_team', to='benchcoach.team'),
),
]

View File

@@ -1,50 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-29 01:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('benchcoach', '0002_alter_player_jersey_number'),
('teamsnap', '0006_auto_20211228_1907'),
]
operations = [
migrations.AlterField(
model_name='availability',
name='benchcoach_object',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_availability', to='benchcoach.availability'),
),
migrations.AlterField(
model_name='event',
name='benchcoach_object',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_event', to='benchcoach.event'),
),
migrations.AlterField(
model_name='lineupentry',
name='benchcoach_object',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_lineupentry', to='benchcoach.positioning'),
),
migrations.AlterField(
model_name='location',
name='benchcoach_object',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_location', to='benchcoach.venue'),
),
migrations.AlterField(
model_name='member',
name='benchcoach_object',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_member', to='benchcoach.player'),
),
migrations.AlterField(
model_name='opponent',
name='benchcoach_object',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_opponent', to='benchcoach.team'),
),
migrations.AlterField(
model_name='team',
name='benchcoach_object',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='teamsnap_team', to='benchcoach.team'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-29 14:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('teamsnap', '0007_auto_20211228_1910'),
]
operations = [
migrations.AlterField(
model_name='availability',
name='status_code',
field=models.SmallIntegerField(choices=[(1, 'Yes'), (0, 'No'), (2, 'Maybe'), (None, 'Unknown')], default=None),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 3.2.6 on 2021-12-29 16:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('teamsnap', '0008_alter_availability_status_code'),
]
operations = [
migrations.AlterField(
model_name='availability',
name='status_code',
field=models.SmallIntegerField(blank=True, choices=[(1, 'Yes'), (0, 'No'), (2, 'Maybe'), (None, 'Unknown')], default=None, null=True),
),
]

View File

@@ -1,14 +0,0 @@
# Generated by Django 3.2.6 on 2022-05-06 15:31
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('teamsnap', '0005_auto_20211224_1042'),
('teamsnap', '0009_alter_availability_status_code'),
]
operations = [
]

View File

@@ -1,221 +1,9 @@
# Create your models here.
from django.db import models
import benchcoach.models
import pyteamsnap.api
from django.utils.timezone import localtime
from benchcoach.users.models import User
class TeamsnapBaseModel(models.Model):
type = None
id = models.CharField(max_length=50, unique=True, primary_key=True)
created_at = models.DateTimeField(null=True)
updated_at = models.DateTimeField(null=True)
ApiObject = pyteamsnap.api.ApiObject
class Meta:
abstract = True
def __str__(self):
return f"TeamSnap {self.__class__.__name__} Object ({self.id})"
@property
def api_url(self):
return "https://api.teamsnap.com/v3/{type}/{id}".format(type=self.type, id=self.id)
class Team(TeamsnapBaseModel):
type = 'team'
name = models.CharField(max_length=50, null=True)
benchcoach_object = models.OneToOneField(
benchcoach.models.Team,
on_delete=models.CASCADE,
related_name="teamsnap_team"
)
ApiObject = pyteamsnap.api.Team
class User(TeamsnapBaseModel):
type = 'user'
first_name = models.CharField(max_length=50, null=True)
last_name = models.CharField(max_length = 50, null=True)
email = models.EmailField(null=True)
managed_teams = models.ManyToManyField(Team)
ApiObject = pyteamsnap.api.User
@classmethod
def update_or_create_from_teamsnap_api(cls, teamsnap_data):
fields = ['id', 'first_name', 'last_name', 'email']
user_data = {k:teamsnap_data[k] for k in fields}
managed_teams = []
for managed_team_id in teamsnap_data['managed_team_ids']:
obj, created = Team.objects.get_or_create(id=managed_team_id)
managed_teams.append(obj)
pass
id = user_data.pop('id')
user, created = cls.objects.update_or_create(id=id, defaults=user_data)
user.managed_teams.add(*managed_teams)
return (user, created)
class TeamsnapManagedObjectModel(TeamsnapBaseModel):
team = models.ForeignKey(
Team,
verbose_name="managed by team",
null=True,
on_delete=models.CASCADE,
)
class Meta:
abstract = True
@property
def url(self, endpoint='view'):
return f"https://go.teamsnap.com/{self.team.id}/{self.type}/{endpoint}/{self.id}"
class Opponent(TeamsnapManagedObjectModel):
type = 'opponent'
name = models.CharField(max_length=50, null=True)
benchcoach_object = models.OneToOneField(
benchcoach.models.Team,
on_delete=models.CASCADE,
related_name="teamsnap_opponent"
)
ApiObject = pyteamsnap.api.Opponent
class Location(TeamsnapManagedObjectModel):
type = 'location'
name = models.CharField(max_length=50, null=True)
benchcoach_object = models.OneToOneField(
benchcoach.models.Venue,
on_delete=models.CASCADE,
related_name="teamsnap_location"
)
ApiObject = pyteamsnap.api.Location
class Member(TeamsnapManagedObjectModel):
# url format is
# f"https://go.teamsnap.com/{self.team.teamsnap_id}/roster/player/{self.teamsnap_id}"
# f"https://go.teamsnap.com/{self.team.teamsnap_id}/roster/edit/{self.teamsnap_id}"
type = 'member'
name = models.CharField(max_length=50, null=True)
benchcoach_object = models.OneToOneField(
benchcoach.models.Player,
on_delete=models.CASCADE,
related_name="teamsnap_member"
)
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(null=True)
ApiObject = pyteamsnap.api.Member
def __str__(self):
return f"{self.last_name}, {self.first_name} ({self.id})"
@property
def name(self):
return f"{self.first_name} {self.last_name}"
class Event(TeamsnapManagedObjectModel):
# url is
# f"https://go.teamsnap.com/{self.team.teamsnap_id}/schedule/view_game/{self.teamsnap_id}"
# f"https://go.teamsnap.com/{self.team.teamsnap_id}/schedule/edit_game/{self.teamsnap_id}"
type = 'event'
benchcoach_object = models.OneToOneField(
benchcoach.models.Event,
on_delete=models.CASCADE,
related_name="teamsnap_event"
)
label = models.CharField(max_length = 50, null=True)
start_date = models.DateTimeField(null=True)
opponent = models.ForeignKey(Opponent, null=True, on_delete=models.CASCADE, related_name="opponent")
location = models.ForeignKey(Location, null=True, on_delete=models.CASCADE)
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(null=True)
game_type = models.CharField(max_length = 50, null=True)
ApiObject = pyteamsnap.api.Event
@property
def csv_event_title(self)->str:
'''
TeamSnap has a title format that it uses for the csv export which includes the date and time
:return: formatted string
'''
# activate(zone)
start_date = localtime(self.start_date)
return f"{self.formatted_title} {start_date:%m/%d/%Y}" + f"{start_date:%-I:%M %p}".rjust(9)
def __str__(self):
return f"{self.formatted_title} ({self.id})"
class Availability(TeamsnapManagedObjectModel):
type='availability'
YES = 1
NO = 0
MAYBE = 2
UNKNOWN = None
status_codes = [
(YES, 'Yes'),
(NO, 'No'),
(MAYBE, 'Maybe'),
(UNKNOWN, 'Unknown')
]
event = models.ForeignKey(Event, null=True, on_delete=models.CASCADE)
member = models.ForeignKey(Member, null=True, on_delete=models.CASCADE)
benchcoach_object = models.OneToOneField(
benchcoach.models.Availability,
on_delete=models.CASCADE,
related_name="teamsnap_availability"
)
status_code = models.SmallIntegerField(choices=status_codes, null=True, blank=True, default=None)
ApiObject = pyteamsnap.api.Availability
def __str__(self):
return f"{self.member} - {self.event} ({self.id})"
class Meta:
verbose_name_plural = "availabilities"
class LineupEntry(TeamsnapManagedObjectModel):
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')
]
benchcoach_object = models.OneToOneField(
benchcoach.models.Positioning,
on_delete=models.CASCADE,
related_name="teamsnap_lineupentry"
)
label = models.PositiveSmallIntegerField(choices=positions, default=None, null=True, blank=True)
sequence = models.PositiveSmallIntegerField(default=0, null=True, blank=True)
ApiObject = pyteamsnap.api.EventLineupEntry
@classmethod
def update_or_create_from_teamsnap_api(cls, teamsnap_data):
fields = [
'id',
'created_at',
'updated_at',
'label',
'sequence'
]
lineup_entry_data = {k: teamsnap_data[k] for k in fields}
member, created = Member.objects.get_or_create(id=teamsnap_data['member_id'])
team, created = Team.objects.get_or_create(id=teamsnap_data['team_id'])
event, created = Event.objects.get_or_create(id=teamsnap_data['event_id'])
id = lineup_entry_data.pop('id')
lineup_entry, created = cls.objects.update_or_create(id=id, defaults=lineup_entry_data)
lineup_entry.team = team
lineup_entry.event = event
lineup_entry.member = member
lineup_entry.save()
return (lineup_entry, created)
class Preferences(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
managed_team_id = models.IntegerField()

31
teamsnap/provider.py Normal file
View File

@@ -0,0 +1,31 @@
from allauth.socialaccount import providers
from allauth.socialaccount.providers.base import ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
class TeamsnapAccount(ProviderAccount):
pass
class TeamsnapProvider(OAuth2Provider):
id = "teamsnap"
name = "TeamSnap"
account_class = TeamsnapAccount
def extract_uid(self, data):
return str(data["id"])
def extract_common_fields(self, data):
return dict(
username=data["email"],
email=data["email"],
first_name=data["first_name"],
last_name=data["last_name"],
)
def get_default_scope(self):
scope = ["read"]
return scope
providers.registry.register(TeamsnapProvider)

View File

@@ -0,0 +1,148 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ title }}{% endblock %}
{% block page_heading %}{% endblock %}
{% block content %}
<h3>Dashboard</h3>
<div class="row">
<div class="col-md pb-2">
<div class="card">
<div class="card-header">
<h4>Upcoming Games</h4>
</div>
<div class="card-body p-0 m-0">
{% for event, availability_summary in events_availabilities|slice:":4" %}
<div class="row m-0 p-2 border-bottom">
<div class="col p-0 m-auto" style="flex: 0 0 100px;">
<div class="d-inline-flex m-0 p-0">
<div class="chart-container" style="height: 100px;width: 100px;">
<canvas id="availability-donut-{{ event.data.id }}" class="availability-donut"
data-event-id="{{ event.data.id }}"
data-available-yes="{{ availability_summary.data.player_going_count }}"
data-available-no="{{ availability_summary.data.player_not_going_count }}"
data-available-maybe="{{ availability_summary.data.player_maybe_count }}"
data-available-unknown="{{ availability_summary.data.player_unknown_count }}"
>
</canvas></div>
</div>
</div>
<div class="col">
<div>
<h4><strong><a class="text-decoration-none text-black" href="{% url 'teamsnap_view_event' team_id=event.data.team_id event_id=event.data.id %}">{{ event.data.formatted_title }}</a></strong></h4>
<h6 class="text-muted mb-2">{{ event.data.start_date|date:"D, F j, g:i A" }}</h6>
<h6 class="text-muted mb-2">{{ event.data.location_name }}</h6>
</div>
<div class="d-flex">
<a class="btn btn-primary btn-sm mx-1" role="button" href="{% url 'teamsnap_edit_lineup' event_ids=event.data.id team_id=event.data.team_id %}">Go to Lineup</a>
<form method="get"
action="{% url 'instagen_generate' team_id=event.data.team_id event_id=event.data.id %}">
<select hidden class="form-select" name="game_id" id="game_id">
<optgroup label="Events">
<option value="" disabled="disabled">Select an event...</option>
<option selected
value="{{ event.data.id }}">{{ event.data.formatted_title }}</option>
</optgroup>
</select>
<input hidden class="form-check-input" type="radio" name="background"
id="backgroundLocation" checked value="location">
<input hidden class="form-check-input" type="radio" name="dimensions" id="square" checked
value="1080x1080">
<button type="submit" class="btn btn-primary btn-sm"><i class="bi bi-instagram"></i>
</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-md pb-2">
<div class="card">
<div class="card-header">
<h4>Past Games</h4>
</div>
<div class="card-body p-0 m-0">
{% for event in ts_events_past|slice:":4" %}
<div class="row m-0 p-2 border-bottom">
<div class="col p-0 m-auto rounded-circle bg-light" style="flex: 0 0 100px;">
<div class="d-inline-flex m-0 p-0">
<div class="d-flex align-items-center justify-content-center" style="height: 100px;width: 100px;">
<h4 class="text-center"><strong>{{ event.data.formatted_results }}</strong></h4>
</div>
</div>
</div>
<div class="col">
<h4 class=""><strong><a class="text-decoration-none text-black" href="{% url 'teamsnap_view_event' team_id=event.data.team_id event_id=event.data.id %}">{{ event.data.formatted_title }}</a></strong></h4>
<h6 class="text-muted mb-2">{{ event.data.start_date|date:"D, F j" }}</h6>
{# <h6 class="text-muted mb-2">{{ event.data.location_name }}</h6><a class="btn btn-primary btn-sm" role="button" href="{% url 'teamsnap_edit_lineup' event_ids=event.data.id team_id=request.user.teamsnapsettings.managed_team.id %}">Go to Lineup</a>#}
<div class="d-flex">
<form method="get"
action="{% url 'instagen_generate' team_id=event.data.team_id event_id=event.data.id %}">
<select hidden class="form-select" name="game_id" id="game_id">
<optgroup label="Events">
<option value="" disabled="disabled">Select an event...</option>
<option selected
value="{{ event.data.id }}">{{ event.data.formatted_title }}</option>
</optgroup>
</select>
<input hidden class="form-check-input" type="radio" name="background"
id="backgroundLocation" checked value="location">
<input hidden class="form-check-input" type="radio" name="dimensions" id="square" checked
value="1080x1080">
<button type="submit" class="btn btn-primary btn-sm"><i class="bi bi-instagram"></i>
</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block inline_javascript %}
<script>
function donut(ctx, yes_count, maybe_count, no_count, unknown_count) {
var style = getComputedStyle(document.body);
const myChart = new Chart(ctx, {
type: 'doughnut',
responsive: 'true',
data: {
datasets: [{
label: 'Availability',
labels: [
'Yes',
'Maybe',
'No',
'Unknown'
],
data: [yes_count, maybe_count, no_count, unknown_count],
backgroundColor: [
style.getPropertyValue('--bs-success'),
style.getPropertyValue('--bs-info'),
style.getPropertyValue('--bs-danger'),
style.getPropertyValue('--bs-secondary')
],
hoverOffset: 4
}]
},
});
}
for (ctx of document.querySelectorAll('.availability-donut')){
donut(ctx,
ctx.dataset.availableYes,
ctx.dataset.availableMaybe,
ctx.dataset.availableNo,
ctx.dataset.availableUnknown,
)
}
</script>
{% endblock %}

View File

@@ -73,4 +73,4 @@
</div>
</form>
{% endblock %}
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ event.data.formatted_title }}{% endblock %}
{% block title %} {{ title }}{% endblock %}
{% block content %}
<div class="card mx-auto" style="max-width: 455px">
@@ -10,7 +10,7 @@
<div class="row">
<div class="col text-end">
<form method="get"
action="{% url "teamsnap_image_generator_generate" team_id=request.user.profile.teamsnapsettings.managed_team.id event_id=event.data.id %}">
action="{% url 'instagen_generate' team_id=event.data.team_id event_id=event.data.id %}">
<select hidden class="form-select" name="game_id" id="game_id">
<optgroup label="Events">
<option value="" disabled="disabled">Select an event...</option>
@@ -26,7 +26,7 @@
</button>
</form>
<a class="btn btn-primary btn-sm py-0 m-1"
href="{% url "teamsnap_image_generator" team_id=request.user.profile.teamsnapsettings.managed_team.id event_id=event.data.id %}"
href="{% url "instagen" team_id=event.data.team_id event_id=event.data.id %}"
role="button">
<div class="d-inline-block"><i class="bi bi-instagram"></i> <i class="bi bi-three-dots"></i>
</div>
@@ -61,7 +61,7 @@
Opponent
</th>
<td>
<a href="{% url 'teamsnap_opponent' team_id=request.user.profile.teamsnapsettings.managed_team.id id=event.data.opponent_id %}">{{ event.data.opponent_name }}</a>
<a href="">{{ event.data.opponent_name }}</a>
</td>
</tr>
<tr>
@@ -69,7 +69,7 @@
Location
</th>
<td>
<a href="{% url 'teamsnap_location' team_id=request.user.profile.teamsnapsettings.managed_team.id id=event.data.location_id %}">{{ event.data.location_name }}</a>
<a href="">{{ event.data.location_name }}</a>
</td>
</tr>
</tbody>
@@ -134,11 +134,11 @@
<div class="row m-0">
<div class="flex-column m-2">
<a class="btn btn-primary btn-sm"
href="{% url 'teamsnap_edit_lineup' event_ids=event.data.id team_id=request.user.profile.teamsnapsettings.managed_team.id %}"
href="{% url 'teamsnap_edit_lineup' team_id=event.data.team_id event_ids=event.data.id %}"
role="button">Edit</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -7,4 +7,4 @@
{% include 'teamsnap/lineup/widgets/lineup.html' with formset_lineup=formset_lineup formset_bench=formset_bench%}
<script src="{% static 'js/Sortable.js' %}"></script>
<script src="{% static 'teamsnap/js/lineup-table.js' %}"></script>
{% endblock %}
{% endblock %}

View File

@@ -28,7 +28,7 @@
{{ form.checked }}
</td>
<td>
<a href="{% url 'teamsnap_view_event' event_id=form.event.data.id team_id=request.user.profile.teamsnapsettings.managed_team.id%}">{{ form.event.data.formatted_title }}</a>
<a href="{% url 'teamsnap_view_event' event_id=form.event.data.id team_id=request.user.teamsnapsettings.managed_team.id%}">{{ form.event.data.formatted_title }}</a>
</td>
<td>
{{ form.event.data.start_date | localtime}}

View File

@@ -0,0 +1,77 @@
{% extends "base.html" %}{% load static %}
{% block title %} Edit Lineups {% endblock %}
{#{% block page_heading %}Edit Lineups{% endblock %}#}
{#{% block page_subheading %}{% endblock %}#}
{% block content %}
{# <div class="container overflow-scroll mx-0 px-0">#}
{# <div class="row flex-row flex-nowrap">#}
{# {% for event_data in contexts %}#}
{# <div class="col border-start border-end">#}
{# <div class = "border-bottom">#}
{# <h4>{{ event_data.event.data.formatted_title }}</h4>#}
{# <h6 class="text-muted" >{{ event_data.data.start_date }}</h6>#}
{# </div>#}
{# {% include 'teamsnap/lineup/widgets/lineup.html' with formset_lineup=event_data.formset_lineup formset_bench=event_data.formset_bench event_id=event_data.event.data.id %}#}
{# </div>#}
{# {% endfor %}#}
{# </div>#}
{# </div>#}
<div class="container overflow-scroll">
<div id="popup-messages-content">
</div>
<div class="row flex-row flex-nowrap">
<table>
<tbody>
<tr class="align-top mx-1">
{% for event_data in contexts %}
<td class="px-1">
{% include "lineup/widgets/lineup.html" with event=event_data.event event_id=event_data.event.data.id formset=event_data.formset formset_startinglineup=event_data.formset_startinglineup formset_bench=event_data.formset_bench formset_out=event_data.formset_out formset_startingpositionalonly=event_data.formset_startingpositionalonly %}
</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block inline_javascript %}
{{ block.super }}
<script src="{% static 'js/Sortable.js' %}"></script>
<script src="{% static 'js/lineup-table.js' %}"></script>
<script>
window.addEventListener('DOMContentLoaded', () => {
/* Run whatever you want */
const postForms = document.querySelectorAll("[id^=form-lineup]");
for (postForm of postForms) {
function handleSubmit(postForm) {
postForm.addEventListener("submit", e => {
e.preventDefault();
formData = new FormData(postForm);
fetch(postForm.action, {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
{#postForm.reset();#}
document.querySelector("#popup-messages-content").innerHTML = `<div class="alert alert-dismissible alert-success" role="alert">
<strong>Success!</strong> ${data.formatted_title} <strong>saved</strong>.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> `
})
.catch((error) => {
console.error('Error:', error);
});
})
}
handleSubmit(postForm)
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,97 @@
{% load static %}
<div class="card mx-auto benchcoach-lineup" style="max-width: 455px" id="benchcoach-lineup-{{ event_id }}">
<form method="post" action='{% url 'teamsnap_submit_lineup' team_id=event.data.team_id event_id=event.data.id %}' id="form-lineup-{{ event.data.id }}">
{{ formset.management_form }}
{% csrf_token %}
<div class="border-bottom p-2">
<h4 class="card-title text-nowrap">{{ event.data.formatted_title }}</h4>
<h6 class="text-muted card-subtitle text-nowrap">{{ event.data.start_date|date:"D, F j, Y g:i A" }}</h6>
<div class="row">
<div class="col">
<button class="btn btn-primary btn-sm py-0 m-1" onclick="importFromClipboard(this)" type="button"><i class="bi bi-arrow-90deg-down"></i></i><i class="bi bi-file-spreadsheet"></i> </button>
</div>
<div class="col text-end d-inline">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle btn-sm py-0 m-1" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-share"></i> Export
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li>
<a class="dropdown-item" href="javascript:;" onclick="copyEmailTable(this, '{{ event.data.start_date|date:"D, F j, Y g:i A" }}, {{ event.data.location_name }}, ({% if event.data.game_type == 'Away' %}@{% endif %}{{ event.data.opponent_name }})', '{% for form in formset %}{{ form.member.data.email_addresses.0 }},{% endfor %}')">
<i class="bi bi-envelope"></i> Generate Lineup Email
</a>
</li>
<li>
<a class="dropdown-item" onclick="sendToClipboard(this)">
<i class="bi bi-file-spreadsheet"></i> Sheet format to Clipboard
</a>
</li>
</ul>
</div>
<div>
<button class="btn btn-teamsnap btn-sm py-0 m-1" type="submit">
<i class="bi bi-arrow-right"></i>
TeamSnap
</button>
</div>
</div>
</div>
</div>
<div class="card-body p-0 m-0">
<div>
<div class="row m-0">
<div class="col border-bottom bg-light">
<i class="bi bi-clipboard-check me-1"></i><span class="text-uppercase fw-bold small">Starting Lineup</span>
</div>
</div>
<div class = "row m-0">
<div class="col border-bottom px-0">
<div class="row mx-0 my-1 position-status">
{% with 'P C 1B 2B 3B SS LF CF RF EH DH' as position_list %}
{% for position in position_list.split %}
<div class="col fw-bold text-center small">
<span class="" id="position-status-{{ position }}">{{ position }}</span>
</div>
{% endfor %}
{% endwith %}
</div>
</div>
</div>
{% include 'lineup/widgets/lineup_table.html' with formset=formset_startinglineup table_id="benchcoach-startinglineup" %}
</div>
<div>
<div class="row m-0">
<div class="col border-bottom bg-light">
<i class="bi bi-clipboard-minus me-1"></i><span class="text-uppercase fw-bold small">Starting (Positional Only)</span>
</div>
</div>
{% include 'lineup/widgets/lineup_table.html' with formset=formset_startingpositionalonly table_id="benchcoach-startingpositionalonly" %}
</div>
<div>
<div class="row m-0">
<div class="col border-bottom bg-light">
<i class="bi bi-clipboard me-1"></i><span class="text-uppercase fw-bold small">Bench</span>
</div>
</div>
</div>
{% include 'lineup/widgets/lineup_table.html' with formset=formset_bench table_id="benchcoach-bench" %}
<div>
<div class="row m-0">
<div class="col border-bottom bg-light">
<i class="bi bi-clipboard-x me-1"></i><span class="text-uppercase fw-bold small">Out</span>
</div>
</div>
</div>
{% include 'lineup/widgets/lineup_table.html' with formset=formset_out table_id="benchcoach-out" %}
</div>
</form>
</div>

View File

@@ -66,4 +66,4 @@
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block content %}
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save">
</form>
{% endblock content %}

View File

@@ -1,22 +1,6 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ title }}{% endblock %}
{% block page_heading %}
<div class="row d-none" >
<div hidden class="col">
<div class="container m-2">
<div class="container m-2">
<div class="btn-group">
<form action="">
<a class="btn btn-sm btn-outline-primary text-nowrap" href="{% url 'teamsnap_schedule' team_id=team_id%}?filters=no_past">No Past Events</a>
<button class="btn btn-sm btn-outline-primary text-nowrap">Games Only</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content %}
{% load tz %}
<div class="">
@@ -51,7 +35,7 @@
{{ event.data.location_name }}
</td>
<td>
<a class="btn btn-outline-secondary btn-sm" href="{% url 'teamsnap_view_event' event_id=event.data.id team_id=request.user.profile.teamsnapsettings.managed_team.id%}"><i class="bi bi-three-dots"></i></a>
<a class="btn btn-outline-secondary btn-sm" href="{% url 'teamsnap_view_event' team_id=event.data.team_id event_id=event.data.id %}"><i class="bi bi-three-dots"></i></a>
</td>
</tr>
{% endfor %}
@@ -61,4 +45,4 @@
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -1,27 +0,0 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ title }}{% endblock %}
{% block page_heading %}Schedule{% endblock %}
{% block content %}
{% load tz %}
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# </thead>#}
<tbody>
{% for event in events %}
<tr>
<td>
<a href="{% url 'teamsnap_event' id=event.data.id team_id=schedule.html %}">{{ event.data.formatted_title }}</a>
</td>
<td>
{{ event.data.start_date | localtime}}
</td>
<td>
{{ event.data.location_name }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@@ -1,150 +0,0 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ title }}{% endblock %}
{% block page_heading %}{% endblock %}
{% block content %}
<h3>Dashboard</h3>
<div class="row">
<div class="col-md pb-2">
<div class="card">
<div class="card-header">
<h4>Upcoming Games</h4>
</div>
<div class="card-body p-0 m-0">
{% for event, availability_summary in events_availabilities|slice:":4" %}
<div class="row m-0 p-2 border-bottom">
<div class="col p-0 m-auto" style="flex: 0 0 100px;">
<div class="d-inline-flex m-0 p-0">
<div class="chart-container" style="height: 100px;width: 100px;">
<canvas id="availability-donut-{{ event.data.id }}" class="availability-donut"
data-event-id="{{ event.data.id }}"
data-available-yes="{{ availability_summary.data.player_going_count }}"
data-available-no="{{ availability_summary.data.player_not_going_count }}"
data-available-maybe="{{ availability_summary.data.player_maybe_count }}"
data-available-unknown="{{ availability_summary.data.player_unknown_count }}"
>
</canvas></div>
</div>
</div>
<div class="col">
<div>
<h4><strong><a class="text-decoration-none text-black" href="{% url 'teamsnap_view_event' team_id=team_id event_id=event.data.id %}">{{ event.data.formatted_title }}</a></strong></h4>
<h6 class="text-muted mb-2">{{ event.data.start_date|date:"D, F j, g:i A" }}</h6>
<h6 class="text-muted mb-2">{{ event.data.location_name }}</h6>
</div>
<div class="d-flex">
<a class="btn btn-primary btn-sm mx-1" role="button" href="{% url 'teamsnap_edit_lineup' event_ids=event.data.id team_id=request.user.profile.teamsnapsettings.managed_team.id %}">Go to Lineup</a>
<form method="get"
action="{% url "teamsnap_image_generator_generate" team_id=request.user.profile.teamsnapsettings.managed_team.id event_id=event.data.id %}">
<select hidden class="form-select" name="game_id" id="game_id">
<optgroup label="Events">
<option value="" disabled="disabled">Select an event...</option>
<option selected
value="{{ event.data.id }}">{{ event.data.formatted_title }}</option>
</optgroup>
</select>
<input hidden class="form-check-input" type="radio" name="background"
id="backgroundLocation" checked value="location">
<input hidden class="form-check-input" type="radio" name="dimensions" id="square" checked
value="1080x1080">
<button type="submit" class="btn btn-primary btn-sm"><i class="bi bi-instagram"></i>
</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-md pb-2">
<div class="card">
<div class="card-header">
<h4>Past Games</h4>
</div>
<div class="card-body p-0 m-0">
{% for event in ts_events_past|slice:":4" %}
<div class="row m-0 p-2 border-bottom">
<div class="col p-0 m-auto rounded-circle bg-light" style="flex: 0 0 100px;">
<div class="d-inline-flex m-0 p-0">
<div class="d-flex align-items-center justify-content-center" style="height: 100px;width: 100px;">
<h4 class="text-center"><strong>{{ event.data.formatted_results }}</strong></h4>
</div>
</div>
</div>
<div class="col">
<h4 class=""><strong><a class="text-decoration-none text-black" href="{% url 'teamsnap_view_event' team_id=team_id event_id=event.data.id %}">{{ event.data.formatted_title }}</a></strong></h4>
<h6 class="text-muted mb-2">{{ event.data.start_date|date:"D, F j" }}</h6>
{# <h6 class="text-muted mb-2">{{ event.data.location_name }}</h6><a class="btn btn-primary btn-sm" role="button" href="{% url 'teamsnap_edit_lineup' event_ids=event.data.id team_id=request.user.profile.teamsnapsettings.managed_team.id %}">Go to Lineup</a>#}
<div class="d-flex">
<form method="get"
action="{% url "teamsnap_image_generator_generate" team_id=request.user.profile.teamsnapsettings.managed_team.id event_id=event.data.id %}">
<select hidden class="form-select" name="game_id" id="game_id">
<optgroup label="Events">
<option value="" disabled="disabled">Select an event...</option>
<option selected
value="{{ event.data.id }}">{{ event.data.formatted_title }}</option>
</optgroup>
</select>
<input hidden class="form-check-input" type="radio" name="background"
id="backgroundLocation" checked value="location">
<input hidden class="form-check-input" type="radio" name="dimensions" id="square" checked
value="1080x1080">
<button type="submit" class="btn btn-primary btn-sm"><i class="bi bi-instagram"></i>
</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<script>
function donut(ctx, yes_count, maybe_count, no_count, unknown_count) {
var style = getComputedStyle(document.body);
const myChart = new Chart(ctx, {
type: 'doughnut',
responsive: 'true',
data: {
datasets: [{
label: 'Availability',
labels: [
'Yes',
'Maybe',
'No',
'Unknown'
],
data: [yes_count, maybe_count, no_count, unknown_count],
backgroundColor: [
style.getPropertyValue('--bs-success'),
style.getPropertyValue('--bs-info'),
style.getPropertyValue('--bs-danger'),
style.getPropertyValue('--bs-secondary')
],
hoverOffset: 4
}]
},
});
}
for (ctx of document.querySelectorAll('.availability-donut')){
donut(ctx,
ctx.dataset.availableYes,
ctx.dataset.availableMaybe,
ctx.dataset.availableNo,
ctx.dataset.availableUnknown,
)
}
</script>
{% endblock %}

View File

@@ -1,28 +0,0 @@
<div class="card">
<div class="row">
<div class="text-center my-2">
<h1><img class="mx-auto" src="{% static 'benchcoach.svg' %}" style="width: 64px;"/>
<strong>Welcome to <span class="text-nowrap">Bench Coach</span></strong>
</h1>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="text-center my-2">
<div class="col-lg-6 m-auto">
<p class="lead mb-4">Quisque at curabitur mollis ornare, malesuada maecenas. Orci elit
tristique,
malesuada eu pharetra. Est praesent tortor porttitor aptent, amet quisque.</p>
{# <div class="d-grid gap-2 d-sm-flex justify-content-sm-center mx-1">#}
{# <button class="btn btn-primary" type="button">Login</button><button class="btn btn-outline-secondary" type="button">Sign Up</button>#}
{# </div>#}
</div>
<div class="container-sm">
{# <ul class="nav nav-pills flex-column mb-auto">#}
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,191 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="{% static 'teamsnap/css/gamecard.css' %}">
<title>Title</title>
</head>
<body class="b5">
<section class="sheet">
<div class="whole-card">
<div class="half-card">
<div class="content card-left">
<table>
<thead>
<tr>
<th colspan="8" class="gametitle">
{{ event.data.formatted_title }} {{ event.data.start_date|date:'m/d/Y g:i A' }}
{# G#01 at Browns 05/01/2021 12:30 PM#}
</th>
<th class="homeaway" colspan="4">{{ event.data.game_type }}</th>
</tr>
</thead>
</table>
<table>
<thead>
<tr>
<th class="numbercell">
</td>
<th class="customcol">
</td>
<th class="numbercell">
</td>
<th class="numbercell">
</td>
<th class="numbercell">1
</td>
<th class="numbercell">2
</td>
<th class="numbercell">3
</td>
<th class="numbercell">4
</td>
<th class="numbercell">5
</td>
<th class="numbercell">6
</td>
<th class="numbercell">7
</td>
<th class="numbercell">X
</td>
</tr>
</thead>
<tbody>
{% for member in members_startinglineup %}
<tr>
<td class="numbercell">{{ member.lineup_entry.sequence | add:"1" }}</td>
<td class="customcol">{{ member.member.last_name }}</td>
<td class="numbercell">{{ member.member.jersey_number }}</td>
<td class="numbercell">{{ member.lineup_entry.label }}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
<table>
<tbody>
{% for member in members_startingpositiononly %}
<tr>
<td class="numbercell"></td>
<td class="customcol">{{ member.member.last_name }}</td>
<td class="numbercell">{{ member.member.jersey_number }}</td>
<td class="numbercell">{{ member.lineup_entry.label }}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="half-card">
<div class="content card-right">
<table class="tg">
<thead>
<tr>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="condensedNameCell">Available</th>
<th class="statscell">AVG/OBP/SLG:PA</th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
<th class="numbercell"></th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td class="numbercell"></td>
<td class="numbercell available-status-code-{{ member.availability.status_code }}">{{ member.member.jersey_number }}</td>
<td class="condensedNameCell available-status-code-{{ member.availability.status_code }}">{{ member.member.last_name }}</td>
<td class="statscell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
<td class="numbercell"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="whole-card">
<div class="half-card">
<div class="content card-left"></div>
</div>
<div class="half-card">
<div class="content card-right">
<div>
<table>
<thead>
<tr>
<th class="numbercell" style="background-color: #323669">
{{ event.data.start_date|date:"D, F j, Y g:i A" }}
</th>
</tr>
<tr>
<th class="numbercell" style="background-color: #323669">
{{ event.data.location_name }}
</th>
</tr>
<tr>
<th class="numbercell" style="background-color: lightgray">
</th>
</tr>
</thead>
</table>
<div>
<div class="" width="100%">
<img src="{% static 'teamsnap/ig/logos/hounds.png' %}"
height="120px"
>
</div>
<div class="" width="100%" style="text-align: center;font-size: xxx-large; font-family: Pacifico">
VS.
</div>
<div class="" width="100%" style="text-align: right">
<img src="{% static 'teamsnap/ig/logos/hounds.png' %}"
width="120px"
>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
</html>

View File

@@ -1,37 +0,0 @@
{% extends "base.html" %}{% load static %}
{% block title %} Edit Lineups {% endblock %}
{#{% block page_heading %}Edit Lineups{% endblock %}#}
{#{% block page_subheading %}{% endblock %}#}
{% block content %}
{# <div class="container overflow-scroll mx-0 px-0">#}
{# <div class="row flex-row flex-nowrap">#}
{# {% for event_data in contexts %}#}
{# <div class="col border-start border-end">#}
{# <div class = "border-bottom">#}
{# <h4>{{ event_data.event.data.formatted_title }}</h4>#}
{# <h6 class="text-muted" >{{ event_data.data.start_date }}</h6>#}
{# </div>#}
{# {% include 'teamsnap/lineup/widgets/lineup.html' with formset_lineup=event_data.formset_lineup formset_bench=event_data.formset_bench event_id=event_data.event.data.id %}#}
{# </div>#}
{# {% endfor %}#}
{# </div>#}
{# </div>#}
<div class="container overflow-scroll">
<div class="row flex-row flex-nowrap">
<table>
<tbody>
<tr class="align-top mx-1">
{% for event_data in contexts %}
<td class="px-1">
{% include "teamsnap/lineup/widgets/lineup.html" with event=event_data.event event_id=event_data.event.data.id formset=event_data.formset formset_startinglineup=event_data.formset_startinglineup formset_bench=event_data.formset_bench formset_out=event_data.formset_out formset_startingpositionalonly=event_data.formset_startingpositionalonly %}
</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
<script src="{% static 'js/Sortable.js' %}"></script>
<script src="{% static 'teamsnap/js/lineup-table.js' %}"></script>
{% endblock %}

View File

@@ -1,77 +0,0 @@
<div class="card mx-auto benchcoach-lineup" style="max-width: 455px" id="benchcoach-lineup-{{ event_id }}">
<form method="post" action="{% url 'teamsnap_submit_lineup' team_id=team_id event_id=event_id%}">
{{ formset.management_form }}
{% csrf_token %}
<div class="border-bottom p-2">
<h4 class="card-title text-nowrap">{{ event.data.formatted_title }}</h4>
<h6 class="text-muted card-subtitle text-nowrap">{{ event.data.start_date|date:"D, F j, Y g:i A" }}</h6>
<div class="row">
<div class="col">
<button class="btn btn-primary btn-sm py-0 m-1" onclick="importFromClipboard(this)" type="button"><i class="bi bi-arrow-90deg-down"></i></i><i class="bi bi-file-spreadsheet"></i> </button>
</div>
<div class="col text-end">
<button class="btn btn-primary btn-sm py-0 m-1" onclick="copyEmailTable(this, '{{ event.data.start_date|date:"D, F j, Y g:i A" }}, {{ event.data.location_name }}, ({% if event.data.game_type == 'Away' %}@{% endif %}{{ event.data.opponent_name }})', '{% for form in formset %}{{ form.member.data.email_addresses.0 }},{% endfor %}')" type="button"><i class="bi bi-arrow-right"></i><i class="bi bi-envelope"></i></button>
<button class="btn btn-primary btn-sm py-0 m-1" onclick="sendToClipboard(this)" type="button"><i class="bi bi-arrow-right"></i><i class="bi bi-file-spreadsheet"></i></button>
<button class="btn btn-success btn-sm py-0 m-1" type="submit"><i class="bi bi-arrow-right"></i><i class="bi bi-asterisk"></i></button>
</div>
</div>
</div>
<div class="card-body p-0 m-0">
<div>
<div class="row m-0">
<div class="col border-bottom bg-light">
<i class="bi bi-clipboard-check me-1"></i><span class="text-uppercase fw-bold small">Starting Lineup</span>
</div>
</div>
<div class = "row m-0">
<div class="col border-bottom px-0">
<div class="row mx-0 my-1 position-status">
{% with 'P C 1B 2B 3B SS LF CF RF EH DH' as position_list %}
{% for position in position_list.split %}
<div class="col fw-bold text-center small">
<span class="" id="position-status-{{ position }}">{{ position }}</span>
</div>
{% endfor %}
{% endwith %}
</div>
</div>
</div>
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_startinglineup table_id="benchcoach-startinglineup" %}
</div>
<div>
<div class="row m-0">
<div class="col border-bottom bg-light">
<i class="bi bi-clipboard-minus me-1"></i><span class="text-uppercase fw-bold small">Starting (Positional Only)</span>
</div>
</div>
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_startingpositionalonly table_id="benchcoach-startingpositionalonly" %}
</div>
<div>
<div class="row m-0">
<div class="col border-bottom bg-light">
<i class="bi bi-clipboard me-1"></i><span class="text-uppercase fw-bold small">Bench</span>
</div>
</div>
</div>
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_bench table_id="benchcoach-bench" %}
<div>
<div class="row m-0">
<div class="col border-bottom bg-light">
<i class="bi bi-clipboard-x me-1"></i><span class="text-uppercase fw-bold small">Out</span>
</div>
</div>
</div>
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_out table_id="benchcoach-out" %}
</div>
</form>
</div>

View File

@@ -1,22 +0,0 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ location.data.name }}{% endblock %}
{% block page_heading %}{{ location.data.name }}{% endblock %}
{% block content %}
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# </thead>#}
<tbody>
{% for key, value in location.data.items %}
<tr>
<th scope="col">
{{ key }}
</th>
<td>
{{ value }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@@ -1,22 +0,0 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ opponent.data.name }}{% endblock %}
{% block page_heading %}{{ opponent.data.name }}{% endblock %}
{% block content %}
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# </thead>#}
<tbody>
{% for key, value in opponent.data.items %}
<tr>
<th scope="col">
{{ key }}
</th>
<td>
{{ value }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@@ -1,22 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<form method="post">
{{ formset.management_form }}{% csrf_token %}
<table class="table">
<tr>
{% for _, field in formset.0.base_fields.items %}
<th>{{ field.label }}</th>
{% endfor %}
</tr>
{% for form in formset %}
<tr>
{% for field in form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
{% endblock %}

View File

@@ -1,8 +0,0 @@
<form method="post" id="formSync" action="{% url 'sync from teamsnap' %}">{% csrf_token %}
<input type="hidden" name="object_name" value="{{ object_name }}">
<input type="hidden" name="object_id" value={{ object_id }}>
<input type="hidden" name="next" value="{{ next }}">
<button type="submit" value="update_event" class="btn btn-sm btn-outline-secondary d-xl-flex align-items-xl-center mx-1">
<i class="bi bi-arrow-clockwise"></i><i class="bi bi-asterisk"></i>
</button>
</form>

View File

@@ -1,3 +1 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -1,26 +1,45 @@
from django.contrib import admin
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from django.urls import path
from django.urls import path, include
from functools import partial
from .provider import TeamsnapProvider
from .views import (
PreferencesFormView,
dashboard,
edit_lineup,
schedule_view,
view_event,
submit_lineup,
gamecard
)
from . import views
urlpatterns = default_urlpatterns(TeamsnapProvider)
urlpatterns = [
path('', views.home, name='teamsnap_home'),
path('<int:team_id>/dashboard/', views.dashboard, name='teamsnap_dashboard'),
path('edit/event/<int:id>', views.edit_event, name='teamsnap edit event'),
path('sync/download', views.sync_from_teamsnap, name="sync from teamsnap"),
path('import/', views.import_teamsnap, name="import"),
path('<int:team_id>/schedule/', views.schedule, name='teamsnap_schedule'),
path('<int:team_id>/schedule/view_event/<int:event_id>', views.event, name='teamsnap_view_event'),
path('<int:team_id>/opponent/view/<int:id>', views.opponent, name='teamsnap_opponent'),
path('<int:team_id>/location/view/<int:id>', views.location, name='teamsnap_location'),
path('<int:team_id>/event/<int:event_ids>/edit_lineup/', views.edit_lineup, name='teamsnap_edit_lineup'),
path('<int:team_id>/event/<str:event_ids>/edit_lineup/', views.edit_lineup, name='teamsnap_edit_multiple_lineups'),
path('<int:team_id>/event/<int:event_id>/submit_lineup/', views.submit_lineup, name='teamsnap_submit_lineup'),
path('<int:team_id>/event/<int:event_id>/gamecard/', views.gamecard, name='teamsnap_gamecard'),
path('<int:team_id>/event/<int:event_id>/image_generator/', views.image_generator, name='teamsnap_image_generator'),
path('<int:team_id>/event/<int:event_id>/image_generator/generate', views.get_matchup_image, name='teamsnap_image_generator_generate'),
path('<int:team_id>/multievent/choose', views.multi_lineup_choose, name='teamsnap_choose_multiple_lineups')
]
urlpatterns += [
path("preferences/", PreferencesFormView.as_view(), name="teamsnap_preferences"),
path("<int:team_id>/schedule/", schedule_view, name="teamsnap_schedule"),
path("<int:team_id>/dashboard/", dashboard, name="teamsnap_dashboard"),
path("dashboard/", dashboard, name="teamsnap_dashboard"),
path("schedule/", schedule_view, name="teamsnap_schedule"),
path(
"<int:team_id>/schedule/view_event/<int:event_id>",
view_event,
name="teamsnap_view_event",
),
path(
"<int:team_id>/schedule/edit_lineup/<int:event_ids>",
edit_lineup,
name="teamsnap_edit_lineup",
),
path(
'<int:team_id>/event/<int:event_id>/submit_lineup/',
submit_lineup,
name='teamsnap_submit_lineup'
),
path('<int:team_id>/event/<str:event_ids>/edit_lineup/',
edit_lineup,
name='teamsnap_edit_multiple_lineups'
),
path('<int:team_id>/schedule/edit_lineup/<int:event_id>/gamecard/',
gamecard,
name='gamecard'),
]

View File

@@ -1,330 +0,0 @@
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFilter, ImageFont
from pathlib import Path
import os
from datetime import datetime
from zoneinfo import ZoneInfo
from typing import List
from dataclasses import dataclass
# image_directory = 'input/images/logos-bw/{filename}.{ext}'
# font_regular_path = "input/fonts/DINAlternate-Bold.ttf"
# font_condensed_path = "input/fonts/DINCondensed-Bold.ttf"
font_regular_path = "benchcoachproject/static/teamsnap/fonts/scala/ScalaSans-BoldLF.otf"
font_condensed_path = "benchcoachproject/static/teamsnap/fonts/scala/ScalaSans-BoldLF.otf"
@dataclass
class Team:
name: str
winlosstie: List[int] = None
image_directory: str = '../input/images/logos-bw/{filename}.{ext}'
@property
def id(self):
return self.name.lower().replace(' ', '-')
@property
def image(self):
path = self.image_directory.format(filename=self.id, ext="png")
if os.path.isfile(path):
return path
else:
return None
@dataclass
class Location:
name: str
address1: str = ""
address2: str = ""
image_directory: str = 'benchcoachproject/static/teamsnap/ig/locations/{filename}.{ext}'
@property
def id(self):
return self.name.lower().replace(' ', '-')
@property
def image(self):
path = self.image_directory.format(filename=self.id, ext="png")
if os.path.isfile(path):
return path
else:
return None
@property
def address(self):
return ",".join([self.address1,self.address2])
args = {
"team_fave" : Team("Hounds"),
"team_opponent" : Team("Trojans"),
"home": False,
"date" : "2021-05-08 12:30 pm",
"location" : Location("Maywood", image_directory="benchcoachproject/static/teamsnap/ig/locations/maywood.{ext}"),
"runs_for": 8,
"runs_against": 9
}
def gen_image (team_fave, team_opponent, date, location=None,
location_name = None,
home=False,
background='location',
address = None,
width = 1080,
height = 1080,
*kwargs,
**args
):
if not isinstance(date, datetime):
# date = parser.parse(date)
# date = date.astimezone(ZoneInfo("America/Chicago"))
pass
if location.image and background == 'location':
background_image = Image.open(location.image).copy()
background_image = background_image.resize((width, height))
# background_image = background_image.filter(ImageFilter.GaussianBlur(radius=5))
background_image = background_image.convert("RGBA")
elif background == 'transparent':
background_image = Image.new('RGBA', (width, height), (0, 0, 0, 0))
else:
background_image = Image.new('RGBA', (width, height), (50, 55, 102))
title_images = []
for team in [team_fave, team_opponent]:
if team.image:
title_images.append(Image.open(team.image).copy())
else:
title_images.append(Image.new('RGBA', (1080, 1080)))
title_image_left = title_images[0]
title_image_right = title_images[1]
# Make a blank image for the rectangle, initialized to a completely
# transparent color.
tmp = Image.new('RGBA', background_image.size, (0, 0, 0, 0))
# Create a drawing context for it.
draw = ImageDraw.Draw(tmp)
# section margin describes the margin of the section rectangles from the sides of the image
section_margin_pct = .05
llx = int(section_margin_pct * background_image.size[0])
urx = int((1 - section_margin_pct) * background_image.size[0])
lly = int((1 - section_margin_pct) * background_image.size[1])
ury = int(.50 * background_image.size[1])
lly2 = int(.49 * background_image.size[1])
ury2 = int(.05 * background_image.size[1])
section_info = Image.open(Path('benchcoachproject/static/teamsnap/ig/graphics/{name}{ext}'.format(name="sign-tan", ext=".png")))
section_info_draw = ImageDraw.Draw(section_info)
section_title = Image.open(Path('benchcoachproject/static/teamsnap/ig/graphics/{name}{ext}'.format(name="sign-green", ext=".png")))
section_title_draw = ImageDraw.Draw(section_title)
# First line: Date
font = ImageFont.truetype(font_regular_path, 62)
text = "{:%a, %B %-d %-I:%M %p}".format(date).upper()
# text = date
text_size = draw.textsize(text, font)
loc = (
1050,
280
)
section_info_draw.text(loc, text, (14,42,28), font=font, anchor="ra")
# Second line: Venue
font = ImageFont.truetype(font_condensed_path, 34)
if not location_name:
text = location.name.upper()
else:
text = location_name.upper()
text_size = section_info_draw.textsize(text, font)
loc = (
1050,
355
)
section_info_draw.text(loc, text, (14,42,28), font=font, anchor="ra")
font = ImageFont.truetype(font_regular_path, 80)
if home:
text = "VS"
else:
text = "AT"
text_size = section_title_draw.textsize(text, font)
loc = (
540,
120
)
color = (255, 255, 255)
section_title_draw.text(loc, text, color, font=font, anchor="mm")
# Alpha composite the two images together.
background_image = Image.alpha_composite(background_image, tmp)
# Title Image Left
title_image_left.thumbnail([350, 350])
loc = (
50, -50
)
section_title.paste(title_image_left, loc, title_image_left)
# Title Image Right
title_image_right.thumbnail([350, 350])
loc = (
650, -50
)
section_title.paste(title_image_right, loc, title_image_right)
# background_image.paste(section_info, (llx, ury), section_info)
# background_image.paste(section_title, (llx, ury2), section_title)
section_title.paste(section_info,(0,0),section_info)
section_title.thumbnail([800, 800])
if background=="badge":
return section_title
background_image.paste(section_title,(
int((background_image.size[0]-section_title.size[0])/2),
height - 360
),section_title)
return background_image
def gen_results_image (team_fave, team_opponent, date,
location=None,
location_name = None,
home=False,
background='location',
address = None,
width = 1080,
height = 1080,
runs_for=0,
runs_against=0,
*kwargs,
**args
):
if not isinstance(date, datetime):
# date = parser.parse(date)
# date = date.astimezone(ZoneInfo("America/Chicago"))
pass
if location.image and background == 'location':
background_image = Image.open(location.image).copy()
background_image = background_image.resize((width, height))
# background_image = background_image.filter(ImageFilter.GaussianBlur(radius=5))
background_image = background_image.convert("RGBA")
elif background == 'transparent':
background_image = Image.new('RGBA', (width, height), (0, 0, 0, 0))
else:
background_image = Image.new('RGBA', (width, height), (50, 55, 102))
title_images = []
for team in [team_fave, team_opponent]:
if team.image:
title_images.append(Image.open(team.image).copy())
else:
title_images.append(Image.new('RGBA', (1080, 1080)))
title_image_left = title_images[0]
title_image_right = title_images[1]
# Make a blank image for the rectangle, initialized to a completely
# transparent color.
tmp = Image.new('RGBA', background_image.size, (0, 0, 0, 0))
# Create a drawing context for it.
draw = ImageDraw.Draw(tmp)
# section margin describes the margin of the section rectangles from the sides of the image
section_margin_pct = .05
llx = int(section_margin_pct * background_image.size[0])
urx = int((1 - section_margin_pct) * background_image.size[0])
lly = int((1 - section_margin_pct) * background_image.size[1])
ury = int(.50 * background_image.size[1])
lly2 = int(.49 * background_image.size[1])
ury2 = int(.05 * background_image.size[1])
#todo fix path
section_info = Image.open(Path('benchcoachproject/static/teamsnap/ig/graphics/{name}{ext}'.format(name="sign-tan", ext=".png")))
section_info_draw = ImageDraw.Draw(section_info)
section_title = Image.open(Path('benchcoachproject/static/teamsnap/ig/graphics/{name}{ext}'.format(name="sign-green", ext=".png")))
section_title_draw = ImageDraw.Draw(section_title)
# First line: Results
loc = (
1050,
265
)
if runs_for > runs_against:
result_letter = "W"
elif runs_for < runs_against:
result_letter = "L"
elif runs_for == runs_against:
result_letter = "T"
font = ImageFont.truetype(font_regular_path, 100)
section_info_draw.text(loc, f"FINAL: {result_letter} {runs_for}-{runs_against}", (14,42,28), font=font, anchor="ra")
# Second line: Date
text = "{:%a, %B %-d %-I:%M %p}".format(date).upper()
# text = date
font = ImageFont.truetype(font_condensed_path, 34)
text_size = section_info_draw.textsize(text, font)
loc = (
1050,
355
)
section_info_draw.text(loc, text, (14,42,28), font=font, anchor="ra")
font = ImageFont.truetype(font_regular_path, 80)
if home:
text = "VS"
else:
text = "AT"
text_size = section_title_draw.textsize(text, font)
loc = (
540,
120
)
color = (255, 255, 255)
section_title_draw.text(loc, text, color, font=font, anchor="mm")
# Alpha composite the two images together.
background_image = Image.alpha_composite(background_image, tmp)
# Title Image Left
title_image_left.thumbnail([350, 350])
loc = (
50, -50
)
section_title.paste(title_image_left, loc, title_image_left)
# Title Image Right
title_image_right.thumbnail([350, 350])
loc = (
650, -50
)
section_title.paste(title_image_right, loc, title_image_right)
# background_image.paste(section_info, (llx, ury), section_info)
# background_image.paste(section_title, (llx, ury2), section_title)
section_title.paste(section_info,(0,0),section_info)
section_title.thumbnail([800, 800])
if background=="badge":
return section_title
background_image.paste(section_title,(
int((background_image.size[0]-section_title.size[0])/2),
height - 360
),section_title)
# background_image.show()
return background_image
# gen_results_image(**args)

View File

@@ -1,525 +0,0 @@
import django.db.models
from typing import List, Tuple
import benchcoach.models
from benchcoach.models import BenchcoachModel, Availability, Player, Team, Positioning, Event, Venue
from pyteamsnap.api import TeamSnap
import pyteamsnap
import teamsnap.models
from django.db.models import QuerySet
from benchcoach.utils.sync_engine import AbstractSyncEngine
class TeamsnapSyncEngine(AbstractSyncEngine):
models = [
Availability,
Player,
Team,
# Positioning, # Not Implemented
Event,
Venue
]
def __init__(self, managed_team_teamsnap_id, teamsnap_token):
self.managed_teamsnap_team_id = managed_team_teamsnap_id
self.client = TeamSnap(token=teamsnap_token)
def _bulk_sync_from_teamsnap(self, qs:QuerySet)-> List[BenchcoachModel]:
'''
Syncs BenchCoach instances (in the form of a QuerySet) from TeamSnap.
This function fetches the actual information from teamsnap, then hands it off to the self._update* functions.
This funciton cuts down on the number of API calls and should be speedier then doing them one by one.
Upating of models from the data is still done one by one.
:param benchcoach_instance: instance to be synced
:return: List of BenchCoach objects that have been processed (but not necessarily changed) during sync.
'''
# I hate having this translation. What I really want is just a property for "teamsnap_object"
# which would simplify all this, but I couldn't figure out how to implement in the
# teamsnap model foreign key and "related_name" that didn't cause conflicts. I don't
# think I need to be too much smarter to figure this out, but alas I am not smart enough.
if qs.model not in self.models:
raise TypeError(f"Sync engine does not sync {qs.model} models")
benchcoachmodel_to_teamsnapfield = {
Availability:'teamsnap_availability',
Player:'teamsnap_member',
Team:'teamsnap_opponent',
# Positioning:'teamsnap_lineupentry', # Not Implemented Yet, but will be 'teamsnap_lineupentry'
Event:'teamsnap_event',
Venue:'teamsnap_location'
}
teamsnapmodel_to_apiobject = {
teamsnap.models.Availability: pyteamsnap.api.Availability,
teamsnap.models.Event: pyteamsnap.api.Event,
# teamsnap.models.LineupEntry:pyteamsnap.api.LineupEntry, # Not implemented Yet
teamsnap.models.Location: pyteamsnap.api.Location,
teamsnap.models.Member: pyteamsnap.api.Member,
teamsnap.models.Opponent: pyteamsnap.api.Opponent,
teamsnap.models.Team: pyteamsnap.api.Team,
# teamsnap.models.User:pyteamsnap.api.User # Not implemented yet
}
apiobject_to_teamsnapmodel = {v:k for k,v in teamsnapmodel_to_apiobject.items()}
if isinstance(qs.first(), benchcoach.models.Team):
# This situation requires special attention because opponents and teams share a table in BenchCoach
if getattr(qs.first(), 'teamsnap_team', None):
teamsnap_attribute_name = 'teamsnap_team'
elif getattr(qs.first(), 'teamsnap_opponent', None):
teamsnap_attribute_name = 'teamsnap_opponent'
else:
teamsnap_attribute_name = benchcoachmodel_to_teamsnapfield.get(type(qs.first()))
ids = [getattr(i, teamsnap_attribute_name).id for i in qs]
ApiObject = teamsnapmodel_to_apiobject.get(type(getattr(qs.first(), teamsnap_attribute_name)))
api_responses = ApiObject.search(client=self.client, id=",".join(ids))
r = []
for api_response in api_responses:
teamsnap_instance = apiobject_to_teamsnapmodel.get(type(api_response)).objects.get(id=api_response.data['id'])
response = self._update_from_teamsnapdata(teamsnap_instance, api_response)
response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, teamsnap_instance.benchcoach_object)
r.append(response)
return r
def _sync_from_teamsnap(self, benchcoach_instance:BenchcoachModel)->BenchcoachModel:
'''
Syncs BenchCoach instance from TeamSnap. This function fetches the actual information from teamsnap, then
hands it off to the self._update* functions.
:param benchcoach_instance: instance to be synced
:return: BenchCoach object that has been processed (but not necessarily changed) during sync.
'''
# I hate having this translation. What I really want is just a property for "teamsnap_object"
# which would simplify all this, but I couldn't figure out how to implement in the
# teamsnap model foreign key and "related_name" that didn't cause conflicts. I don't
# think I need to be too much smarter to figure this out, but alas I am not smart enough.
benchcoachmodel_to_teamsnapfield = {
Availability:'teamsnap_availability',
Player:'teamsnap_member',
Team:'teamsnap_opponent',
# Positioning:'teamsnap_lineupentry', # Not Implemented Yet, but will be 'teamsnap_lineupentry'
Event:'teamsnap_event',
Venue:'teamsnap_location'
}
teamsnapmodel_to_apiobject = {
teamsnap.models.Availability: pyteamsnap.api.Availability,
teamsnap.models.Event: pyteamsnap.api.Event,
# teamsnap.models.LineupEntry:pyteamsnap.api.LineupEntry, # Not implemented Yet
teamsnap.models.Location: pyteamsnap.api.Location,
teamsnap.models.Member: pyteamsnap.api.Member,
teamsnap.models.Opponent: pyteamsnap.api.Opponent,
teamsnap.models.Team: pyteamsnap.api.Team,
# teamsnap.models.User:pyteamsnap.api.User # Not implemented yet
}
if isinstance(benchcoach_instance, benchcoach.models.Team):
# This situation requires special attention because opponents and teams share a table in BenchCoach
teamsnap_instance = getattr(benchcoach_instance, 'teamsnap_team', None)
if not teamsnap_instance: teamsnap_instance = getattr(benchcoach_instance, 'teamsnap_opponent')
else:
teamsnap_instance = getattr(benchcoach_instance, benchcoachmodel_to_teamsnapfield.get(type(benchcoach_instance)))
ApiObject = teamsnapmodel_to_apiobject.get(type(teamsnap_instance))
api_response = ApiObject.get(self.client, teamsnap_instance.id)
r = self._update_from_teamsnapdata(teamsnap_instance, api_response)
r = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
return r
def _update_from_teamsnapdata(self, teamsnap_instance:teamsnap.models.TeamsnapBaseModel, teamsnap_data: pyteamsnap.api.ApiObject) -> teamsnap.models.TeamsnapBaseModel:
''''''
if isinstance(teamsnap_data, pyteamsnap.api.ApiObject):
teamsnap_data = teamsnap_data.data
else:
raise TypeError
if not teamsnap_data['type'] == teamsnap_instance.type:
raise Exception()
data_type = teamsnap_data['type']
fields = ['id', 'created_at', 'updated_at']
related_objects = {}
if data_type in ['opponent', 'team', 'location']:
fields += ['name']
elif data_type == 'event':
fields += [
'label',
'start_date',
'formatted_title',
'points_for_opponent',
'points_for_team',
'is_game',
'game_type'
]
if teamsnap_data.get('location_id'):
related_objects['location'] = teamsnap.models.Location.objects.get(id=teamsnap_data['location_id'])
if teamsnap_data.get('opponent_id'):
related_objects['opponent'] = teamsnap.models.Opponent.objects.get(id=teamsnap_data['opponent_id'])
pass
elif data_type == 'member':
fields += [
'first_name',
'last_name',
'jersey_number',
'is_non_player'
]
elif data_type == 'availability':
fields += ['status_code']
related_objects['member'] = teamsnap.models.Member.objects.get(id=teamsnap_data['member_id'])
related_objects['event'] = teamsnap.models.Event.objects.get(id=teamsnap_data['event_id'])
else:
raise ValueError
if teamsnap_data.get('team_id'):
related_objects['team'] = teamsnap.models.Team.objects.filter(id=teamsnap_data['team_id']).first()
for field in fields:
value = teamsnap_data.get(field)
# if value is None:
# continue
# else:
setattr(teamsnap_instance,field,value)
for related_object_name, related_object in related_objects.items():
setattr(teamsnap_instance, related_object_name, related_object)
teamsnap_instance.save()
return teamsnap_instance
def _update_teamsnapdb_to_benchcoachdb(self, teamsnap_instance, benchcoach_instance) -> List[Tuple[django.db.models.Model, bool]]:
if isinstance(teamsnap_instance, teamsnap.models.Event):
benchcoach_model = Event
d = {
'start': teamsnap_instance.start_date,
}
if teamsnap_instance.team:
if teamsnap_instance.team.benchcoach_object:
if teamsnap_instance.game_type == "Home":
d['home_team'] = teamsnap_instance.team.benchcoach_object
elif teamsnap_instance.game_type == "Away":
d['away_team'] = teamsnap_instance.team.benchcoach_object
elif not teamsnap_instance.team.benchcoach_object:
raise Team.DoesNotExist
if teamsnap_instance.opponent:
if teamsnap_instance.opponent.benchcoach_object:
if teamsnap_instance.game_type == 'Home':
d['away_team'] = teamsnap_instance.opponent.benchcoach_object
elif teamsnap_instance.game_type == 'Away':
d['home_team'] = teamsnap_instance.opponent.benchcoach_object
elif not teamsnap_instance.opponent.benchcoach_object:
raise Team.DoesNotExist
pass
if teamsnap_instance.location:
if teamsnap_instance.location.benchcoach_object:
if teamsnap_instance.location:
d['venue'] = teamsnap_instance.location.benchcoach_object
elif not teamsnap_instance.location.benchcoach_object:
raise Venue.DoesNotExist
elif isinstance(teamsnap_instance, teamsnap.models.Opponent):
benchcoach_model = Team
d = {
'name': teamsnap_instance.name,
}
elif isinstance(teamsnap_instance, teamsnap.models.Team):
benchcoach_model = Team
d = {
'name': teamsnap_instance.name,
}
elif isinstance(teamsnap_instance, teamsnap.models.Location):
benchcoach_model = Venue
d = {
'name': teamsnap_instance.name,
}
elif isinstance(teamsnap_instance, teamsnap.models.Member):
benchcoach_model = Player
d = {
'first_name': teamsnap_instance.first_name,
'last_name': teamsnap_instance.last_name,
'jersey_number': teamsnap_instance.jersey_number,
}
elif isinstance(teamsnap_instance, teamsnap.models.Availability):
benchcoach_model = Availability
translation = {
teamsnap_instance.YES: Availability.YES,
teamsnap_instance.NO: Availability.NO,
teamsnap_instance.MAYBE: Availability.MAYBE
}
d = {
'available': translation.get(teamsnap_instance.status_code, Availability.UNKNOWN),
'player': teamsnap_instance.member.benchcoach_object,
'event': teamsnap_instance.event.benchcoach_object
}
r = []
if teamsnap_instance.member.benchcoach_object:
d['player'] = teamsnap_instance.member.benchcoach_object
elif not teamsnap_instance.member.benchcoach_object:
raise Player.DoesNotExist
if teamsnap_instance.event.benchcoach_object:
d['event'] = teamsnap_instance.event.benchcoach_object
elif not teamsnap_instance.event.benchcoach_object:
raise Event.DoesNotExist
else:
raise ValueError
for field, value in d.items():
setattr(benchcoach_instance, field, value)
benchcoach_instance.save()
teamsnap_instance.benchcoach_object = benchcoach_instance
teamsnap_instance.save()
return benchcoach_instance
def _find_counterpart(self, instance):
'''
find the counterpart BenchCoach object from the TeamSnap object.
NOT CURRENTLY USED.
:param instance:
:return:
'''
instance_type = type(instance)
counterpart_instance = None
if instance_type == Availability:
counterpart_instance = instance.teamsnap_availability
elif instance_type == Player:
counterpart_instance = instance.teamsnap_member
elif instance_type == Event:
counterpart_instance = instance.teamsnap_event
elif instance_type == Venue:
counterpart_instance = instance.teamsnap_location
elif instance_type == Team:
if hasattr(instance, 'teamsnap_opponent'):
counterpart_instance = instance.teamsnap_opponent
elif hasattr(instance, 'teamsnap_team'):
counterpart_instance = instance.teamsnap_team
else:
raise ValueError("instance doesn't seem to be an teamsnap opponent or a teamsnap team")
elif instance_type == Positioning:
counterpart_instance = instance.teamsnap_lineupentry
else:
raise Exception()
return counterpart_instance
def _sync_qs (self, qs, direction):
if direction == 'download':
if qs.model not in self.models:
raise TypeError(f"Sync engine does not sync {qs.model} models")
r=[]
r = self._bulk_sync_from_teamsnap(qs)
elif direction == 'upload':
raise NotImplementedError('Uploading not supported by this sync engine yet.')
else:
raise TypeError(f"Direction {direction} not supported. 'upload' or 'download' must be specified")
return r
def _sync_instance(self, instance, direction, data=None):
r=[]
if direction == 'download':
r.append(self._sync_from_teamsnap(instance))
elif direction == 'upload':
raise NotImplementedError('Uploading not supported by this sync engine yet.')
else:
raise TypeError(f"Direction {direction} not supported. 'upload' or 'download' must be specified")
return r
def sync(self, qs: django.db.models.QuerySet = None, instance: benchcoach.models.BenchcoachModel = None,
direction='download') -> List[Tuple[django.db.models.Model, bool]]:
if not isinstance(qs, QuerySet) and not isinstance(instance, benchcoach.models.BenchcoachModel):
raise TypeError(f"sync requires either a QuerySet or model instance to be provided")
if qs and instance:
raise TypeError(f"sync requires either a QuerySet or model instance to be provided, but not both")
elif qs:
r = self._sync_qs(qs, direction)
elif instance:
r = self._sync_instance(instance, direction)
return r
def import_items(self):
'''
Implementation of import items from the abstract base class AbstractSyncEngine.
Imports objects from TeamSnap into BenchCoach, creating BenchCoach objects when necessary.
It runs through all supported TeamSnap Objects every execution.
NOTE: The number of availability objects causes this function to choke, so consider not updating
those on import.
:return:
'''
['team', 'opponent', 'location', 'member', 'event', 'availability']
# the common kwargs for searching the API. Most objects just need the client and currently managed team id.
kwargs = {'client':self.client,'team_id': self.managed_teamsnap_team_id}
# r is the result dictionary, the key is the name of the benchcoach object, and the value is the list of BenchCoach objects that
# have been iterated (but not necessarily changed) during this import.
r = {}
# Walking through each TeamSnap object. There is a fair amount of repetition that could use clean-up.
# ---team---
r['team'] = []
# Search API for objects belonging to currently managed team, and iterate
for teamsnap_data in pyteamsnap.api.Team.search(client=self.client, id=self.managed_teamsnap_team_id):
# check if TeamSnap ID already exists in the Teamsnap DB.
if teamsnap.models.Team.objects.filter(id=teamsnap_data.data['id']):
teamsnap_instance = teamsnap.models.Team.objects.filter(id=teamsnap_data.data['id']).first()
# If it does, retrieve the BenchCoach instance attached.
# It is enforced (by this import function) that every teamsnap instance has a related BenchCoach instance attached.
# No other function can create TeamSnap instances (or create related BenchCoach instances from the TeamSnap service)
benchcoach_instance = teamsnap_instance.benchcoach_object
else:
# Otherwise create TeamSnap instance
teamsnap_instance = teamsnap.models.Team()
# and create related BenchCoach instance
benchcoach_instance = benchcoach.models.Team()
# and attach it to the BenchCoach instance
teamsnap_instance.benchcoach_object=benchcoach_instance
benchcoach_instance.save()
# Now, update the data from the API to the retrieved/created instances
response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
teamsnap_instance.save()
response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
r['team'].append(response)
# ---opponent---
# See first object for additional comments on the steps followed.
# Opponents from teamsnap go to the BenchCoach "Team" database.
# Dependent on Team. These objects need to be available to attach as related objects or the functions
# self._update_from teamsnapdata and self.update_teamsnapdb_to_benchcoachdb may fail.
for teamsnap_data in pyteamsnap.api.Opponent.search(**kwargs):
if teamsnap.models.Opponent.objects.filter(id=teamsnap_data.data['id']):
teamsnap_instance = teamsnap.models.Opponent.objects.filter(id=teamsnap_data.data['id']).first()
benchcoach_instance = teamsnap_instance.benchcoach_object
else:
teamsnap_instance = teamsnap.models.Opponent()
benchcoach_instance = benchcoach.models.Team()
teamsnap_instance.benchcoach_object = benchcoach_instance
benchcoach_instance.save()
response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
r['team'].append(response)
# ---location---
# See first object for additional comments on the steps followed.
# Dependent on Team. These objects need to be available to attach as related objects or the functions
# self._update_from teamsnapdata and self.update_teamsnapdb_to_benchcoachdb may fail.
r['location'] = []
for teamsnap_data in pyteamsnap.api.Location.search(**kwargs):
if teamsnap.models.Location.objects.filter(id=teamsnap_data.data['id']):
teamsnap_instance = teamsnap.models.Location.objects.filter(id=teamsnap_data.data['id']).first()
benchcoach_instance = teamsnap_instance.benchcoach_object
else:
teamsnap_instance = teamsnap.models.Location()
benchcoach_instance = benchcoach.models.Venue()
teamsnap_instance.benchcoach_object = benchcoach_instance
benchcoach_instance.save()
response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
r['location'].append(response)
# ---member---
# See first object for additional comments on the steps followed.
# Dependent on Team. These objects need to be available to attach as related objects or the functions
# self._update_from teamsnapdata and self.update_teamsnapdb_to_benchcoachdb may fail.
r['member'] = []
# Search API for members to import. Note: Non players are not included in sync.
for teamsnap_data in pyteamsnap.api.Member.search(**kwargs,
is_non_player = False
):
if teamsnap_data.data['is_non_player'] == True:
continue
if teamsnap.models.Member.objects.filter(id=teamsnap_data.data['id']):
teamsnap_instance = teamsnap.models.Member.objects.filter(id=teamsnap_data.data['id']).first()
benchcoach_instance = teamsnap_instance.benchcoach_object
else:
teamsnap_instance = teamsnap.models.Member()
benchcoach_instance = benchcoach.models.Player()
teamsnap_instance.benchcoach_object = benchcoach_instance
benchcoach_instance.save()
response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
r['member'].append(response)
# ---event---
# See first object for additional comments on the steps followed.
# Dependent on Team, Opponent, Location. These objects need to be available to attach as related objects or the functions
# self._update_from teamsnapdata and self.update_teamsnapdb_to_benchcoachdb may fail.
r['event'] = []
for teamsnap_data in pyteamsnap.api.Event.search(**kwargs):
if teamsnap.models.Event.objects.filter(id=teamsnap_data.data['id']):
teamsnap_instance = teamsnap.models.Event.objects.filter(id=teamsnap_data.data['id']).first()
benchcoach_instance = teamsnap_instance.benchcoach_object
else:
teamsnap_instance = teamsnap.models.Event()
benchcoach_instance = benchcoach.models.Event()
teamsnap_instance.benchcoach_object = benchcoach_instance
benchcoach_instance.save()
response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
r['event'].append(response)
# ---availability---
# See first object for additional comments on the steps followed.
# Availability was a bit tricky to implement, because there are "not null" contstraints for the Availability object in Bench Coach
# Ideally, there probably should be more "not null" constraints on more of the BenchCoach models, so a generalized function should
# look more like this than the ones above.
# Dependent on Team, Member, Event. These objects need to be available to attach as related objects or the functions
# self._update_from teamsnapdata and self.update_teamsnapdb_to_benchcoachdb may fail.
#TODO this import is wonky and causes errors on servers. maybe skip this import, or do it in chunks?
r['availability'] = []
# Search API for members to import. Note: Non players are not included in sync.
player_ids = [member.id for member in teamsnap.models.Member.objects.filter(is_non_player=False)]
for teamsnap_data in pyteamsnap.api.Availability.search(**kwargs,
member_id=",".join(player_ids)
):
if teamsnap.models.Availability.objects.filter(id=teamsnap_data.data['id']):
teamsnap_instance = teamsnap.models.Availability.objects.filter(id=teamsnap_data.data['id']).first()
benchcoach_instance = teamsnap_instance.benchcoach_object
else:
teamsnap_instance = teamsnap.models.Availability()
event_instance = benchcoach.models.Event.objects.get(teamsnap_event__id=teamsnap_data.data['event_id'])
player_instance = benchcoach.models.Player.objects.get(teamsnap_member__id=teamsnap_data.data['member_id'])
benchcoach_instance = benchcoach.models.Availability(event=event_instance, player=player_instance)
benchcoach_instance.save()
teamsnap_instance.benchcoach_object_id = benchcoach_instance.id
response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data)
response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
r['availability'].append(response)
return r

View File

@@ -1,51 +0,0 @@
from django.test import TestCase
import os
from teamsnap.utils.teamsnap_sync_engine import TeamsnapSyncEngine
import benchcoach.models
import teamsnap.models
TEAMSNAP_TOKEN = os.environ['TEAMSNAP_TOKEN']
TEAM_TEAMSNAP_ID = os.environ['TEAM_TEAMSNAP_ID']
pass
# syncengine = TeamsnapSyncEngine(managed_team_teamsnap_id=TEAM_TEAMSNAP_ID, teamsnap_token=TEAMSNAP_TOKEN)
# r = syncengine.import_items()
class TestSync(TestCase):
fixtures = ['minimal']
def setUp(self):
self.syncengine = TeamsnapSyncEngine(managed_team_teamsnap_id=TEAM_TEAMSNAP_ID, teamsnap_token=TEAMSNAP_TOKEN)
r = self.syncengine.import_items()
pass
def test_syncengine(self):
# test that the import can be run again
r = self.syncengine.import_items()
benchcoach_objects = {
'availability': list(benchcoach.models.Availability.objects.all()),
'event': list(benchcoach.models.Event.objects.all()),
'player': list(benchcoach.models.Player.objects.all()),
'positioning': list(benchcoach.models.Positioning.objects.all()),
'team': list(benchcoach.models.Team.objects.all()),
'venue': list(benchcoach.models.Venue.objects.all())
}
teamsnap_objects = {
'availability': list(teamsnap.models.Availability.objects.all()),
'event': list(teamsnap.models.Event.objects.all()),
'member': list(teamsnap.models.Member.objects.all()),
'lineupentry': list(teamsnap.models.LineupEntry.objects.all()),
'team': list(teamsnap.models.Team.objects.all()),
'opponent': list(teamsnap.models.Opponent.objects.all()),
'location': list(teamsnap.models.Location.objects.all())
}
self.assertIsNotNone(r)
def test_all_models(self):
pass
self.syncengine.sync(qs=benchcoach.models.Event.objects.all())
breakpoint()
pass

View File

@@ -1,370 +1,400 @@
import operator
import time
from django.shortcuts import render, redirect
from .models import User, Member, Team, Event, Location, LineupEntry, Opponent, Availability
from django.http import HttpResponse, HttpResponseNotAllowed
import benchcoach.models
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .utils.teamsnap_sync_engine import TeamsnapSyncEngine
from django.templatetags.static import static
import datetime
import re
@login_required()
def edit_event(request, id):
'''
redirect to teamsnap.com page for editing of event.
:param request:
:param id:
:return:
'''
event = Event.objects.get(id = id)
return redirect(event.edit_url)
import requests
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)
from django.shortcuts import redirect, render
from django.views.generic.edit import FormView
from django.http import HttpResponseNotAllowed, HttpResponse, JsonResponse, HttpResponseServerError
@login_required()
def home(request):
current_benchcoach_user = request.user
current_teamsnap_user = request.user.profile.teamsnap_user
current_teamsnap_team = request.user.profile.teamsnapsettings.managed_team
teamsnap_objects = {}
for teamsnap_obj, benchcoach_object in [
(Availability, benchcoach.models.Availability),
(Event, benchcoach.models.Event),
(LineupEntry, benchcoach.models.Positioning),
(Location, benchcoach.models.Venue),
(Member, benchcoach.models.Player),
(Opponent, benchcoach.models.Team),
(Team, benchcoach.models.Team),
# (User, {'name':})
]:
teamsnap_objects[teamsnap_obj.__name__.lower()] = {}
teamsnap_objects[teamsnap_obj.__name__.lower()]['object_count'] = teamsnap_obj.objects.count()
if benchcoach_object:
teamsnap_objects[teamsnap_obj.__name__.lower()]['counterpart'] = {'name':benchcoach_object.__name__.lower()}
teamsnap_objects[teamsnap_obj.__name__.lower()]['counterpart']['object_count'] = benchcoach_object.objects.count()
from .forms import PreferencesForm
from .models import Preferences
from .provider import TeamsnapProvider
import pyteamsnap.api
context= {
'benchcoach_user': current_benchcoach_user,
'teamsnap_user': current_teamsnap_user,
'teamsnap_team':current_teamsnap_team,
'teamsnap_objects': teamsnap_objects
}
return render(request, 'teamsnap/home.html', context)
class TeamsnapAdapter(OAuth2Adapter):
provider_id = TeamsnapProvider.id
@login_required()
def dashboard(request, team_id):
current_benchcoach_user = request.user
current_teamsnap_user = request.user.profile.teamsnap_user
current_teamsnap_team = request.user.profile.teamsnapsettings.managed_team
teamsnap_objects = {}
# Fetched programmatically, must be reachable from container
access_token_url = "{}/oauth/token/".format("https://auth.teamsnap.com")
profile_url = "{}/me/".format("https://api.teamsnap.com/v3/")
TEAM_ID = team_id
TOKEN = request.user.profile.teamsnap_access_token
no_past = bool(request.GET.get('no_past', 0))
games_only = bool(request.GET.get('games_only', 0))
from pyteamsnap.api import TeamSnap, Event, AvailabilitySummary
client = TeamSnap(token=TOKEN)
time.sleep(.5)
ts_events = Event.search(client, team_id=TEAM_ID)
ts_availability_summaries_d = {a.data['id']:a for a in AvailabilitySummary.search(client, team_id=team_id)}
ts_events_future = [e for e in ts_events if e.data['start_date'] > datetime.datetime.now(datetime.timezone.utc)]
ts_events_past = [e for e in reversed(ts_events) if e.data['start_date'] < datetime.datetime.now(datetime.timezone.utc)]
# Accessed by the user browser, must be reachable by the host
authorize_url = "{}/oauth/authorize/".format("https://auth.teamsnap.com/")
return render(request, 'teamsnap/dashboard.html', {
'team_id':team_id,
'ts_events_future':ts_events_future,
'ts_events_past': ts_events_past,
'events_availabilities' : [(e, ts_availability_summaries_d[e.data['id']]) for e in ts_events_future]
})
# NOTE: trailing slashes in URLs are important, don't miss it
@login_required()
def sync_from_teamsnap(request, object_name=None, object_id=None):
if request.POST:
next = request.POST.get('next')
object_name = request.POST.get('object_name')
object_id = request.POST.get('object_id')
def complete_login(self, request, app, token, **kwargs):
headers = {"Authorization": f"Bearer {token.token}"}
resp = requests.get(self.profile_url, headers=headers)
j = resp.json()
if j.get("collection", {}).get("items"):
extra_data = {
i["name"]: i["value"] for i in j["collection"]["items"][0]["data"]
}
return self.get_provider().sociallogin_from_response(request, extra_data)
Object = {
obj.__name__.lower(): obj
for obj in
[Availability, Event, LineupEntry, Location, Member, Opponent, Team, User]
}.get(object_name)
def populate_user(self, request, sociallogin, data):
user = super().populate_user(request, sociallogin, data)
user.username = user.email
return user
TEAM_ID = request.user.profile.teamsnapsettings.managed_team.id
TOKEN = request.user.profile.teamsnap_access_token
sync_engine = TeamsnapSyncEngine(teamsnap_token=TOKEN, managed_team_teamsnap_id=TEAM_ID)
r = {}
oauth2_login = OAuth2LoginView.adapter_view(TeamsnapAdapter)
oauth2_callback = OAuth2CallbackView.adapter_view(TeamsnapAdapter)
r[object_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()
if object_name == 'team':
if object_id:
r[object_name] = sync_engine.sync(qs=benchcoach.models.Team.objects.filter(id=object_id))
else:
r[object_name] = sync_engine.sync(qs=benchcoach.models.Team.objects.all())
ts_token = (
current_teamsnap_user.socialtoken_set.order_by("-expires_at").first().token
)
if object_name == 'venue':
if object_id:
r[object_name] = sync_engine.sync(qs=benchcoach.models.Venue.objects.filter(id=object_id))
else:
r[object_name] = sync_engine.sync(qs=benchcoach.models.Venue.objects.all())
return pyteamsnap.api.TeamSnap(token=ts_token)
if object_name == 'player':
if object_id:
r[object_name] = sync_engine.sync(qs=benchcoach.models.Player.objects.filter(id=object_id))
else:
r[object_name] = sync_engine.sync(qs=benchcoach.models.Player.objects.all())
class PreferencesFormView(FormView):
template_name = "preferences.html"
form_class = PreferencesForm
success_url = "/"
if object_name == 'event':
if object_id:
r[object_name] = sync_engine.sync(qs=benchcoach.models.Event.objects.filter(id=object_id))
r['availability'] = sync_engine.sync(qs=benchcoach.models.Event.objects.get(id=object_id).availability_set.all())
else:
r[object_name] = sync_engine.sync(qs=benchcoach.models.Event.objects.all())
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):
form.save()
return super().form_valid(form)
if object_name == 'availability':
r[object_name] = []
if object_id:
r[object_name] += sync_engine.sync(qs=benchcoach.models.Availability.objects.filter(id=object_id))
else:
for event in benchcoach.models.Player.objects.all():
r[object_name] += sync_engine.sync(qs=event.availability_set.all())
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
initial = super().get_initial()
for object_name, results in r.items():
if len(results) == 0:
messages.error(request, f"Error! No {object_name} objects updated")
else:
messages.success(request, f"Success! {len(results)} {object_name} objects updated.")
initial["user"] = self.request.user
# initial['managed_team_id']
return redirect(next)
else:
return HttpResponse(404)
return initial
@login_required()
def import_teamsnap(request):
TEAM_ID = request.user.profile.teamsnapsettings.managed_team.id
TOKEN = request.user.profile.teamsnap_access_token
def get_form(self):
"""
Returns the initial data to use for forms on this view.
"""
import pyteamsnap
sync_engine = TeamsnapSyncEngine(teamsnap_token=TOKEN, managed_team_teamsnap_id=TEAM_ID)
r = sync_engine.import_items()
ts_account = self.request.user.socialaccount_set.first()
ts_token = ts_account.socialtoken_set.first()
# ts_token =
ts = pyteamsnap.TeamSnap(token=ts_token)
for object_name, results in r.items():
if len(results) == 0:
messages.error(request, f"Error! No {object_name} objects created or updated")
else:
messages.success(request, f"Success! {len(results)} {object_name} objects imported")
me = pyteamsnap.api.Me(ts)
return redirect('teamsnap home')
teams = [
(id, pyteamsnap.api.Team.get(ts, id=id))
for id in me.data["managed_team_ids"]
]
@login_required()
def schedule(request, team_id):
TEAM_ID = team_id
TOKEN = request.user.profile.teamsnap_access_token
no_past = bool(request.GET.get('no_past', 0))
games_only = bool(request.GET.get('games_only',0))
from pyteamsnap.api import TeamSnap, Event, Location, Opponent
client = TeamSnap(token=TOKEN)
time.sleep(.5)
ts_events = Event.search(client, team_id=TEAM_ID)
try:
contact = Preferences.objects.get(user=self.request.user)
form = PreferencesForm(instance=contact, **self.get_form_kwargs())
except Preferences.DoesNotExist:
form = super().get_form(self.form_class)
choices = [
(id, f"{team.data['name']} ({team.data['season_name']})")
for id, team in teams
]
form.fields["managed_team_id"].widget.choices = choices
return form
def schedule_view(request, team_id=None):
if not team_id:
return redirect(
"teamsnap_schedule", team_id=request.user.preferences.managed_team_id
)
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
ts_events = Event.search(client, team_id=team_id)
if no_past:
ts_events = [e for e in ts_events if e.data['start_date'] > datetime.datetime.now(datetime.timezone.utc)]
ts_events = [
e
for e in ts_events
if e.data["start_date"] > datetime.datetime.now(datetime.timezone.utc)
]
if games_only:
ts_events = [e for e in ts_events if e.data['is_game']]
ts_events = {e.data['id']:e for e in ts_events}
# ts_opponents = {o.data['id']:o for o in Opponent.search(client, team_id=TEAM_ID)}
# ts_locations = {l.data['id']:l for l in Location.search(client, team_id=TEAM_ID)}
# for event in ts_events:
ts_events = [e for e in ts_events if e.data["is_game"]]
ts_events = {e.data["id"]: e for e in ts_events}
pass
return render(request, "teamsnap/schedule.html", context={"events":ts_events.values(), "team_id":team_id})
return render(
request,
"schedule.html",
context={"events": ts_events.values(), "team_id": team_id},
)
@login_required()
def event(request, event_id, team_id):
TOKEN = request.user.profile.teamsnap_access_token
from pyteamsnap.api import TeamSnap, Event, Availability, Member, EventLineupEntry, EventLineup, AvailabilitySummary
client = TeamSnap(token=TOKEN)
time.sleep(0.5)
ts_bulkload = client.bulk_load(team_id=team_id,
types=[Event, EventLineup, EventLineupEntry, AvailabilitySummary, Member],
event__id=event_id)
def view_event(request, event_id, team_id=None):
if not team_id:
return redirect(
"teamsnap_event", team_id=request.user.preferences.managed_team_id
)
from pyteamsnap.api import (
AvailabilitySummary,
Event,
EventLineup,
EventLineupEntry,
Member,
)
client = get_teamsnap_client(request)
ts_bulkload = client.bulk_load(
team_id=team_id,
types=[Event, EventLineup, EventLineupEntry, AvailabilitySummary, Member],
event__id=event_id,
)
ts_event = [i for i in ts_bulkload if isinstance(i, Event)][0]
# ts_availabilities = Availability.search(client, event_id=ts_event.data['id'])
ts_availability_summary = \
[i for i in ts_bulkload if isinstance(i, AvailabilitySummary) and i.data['event_id'] == event_id][0]
ts_lineup_entries = [i for i in ts_bulkload if isinstance(i, EventLineupEntry) and i.data['event_id'] == event_id]
ts_availability_summary = [
i
for i in ts_bulkload
if isinstance(i, AvailabilitySummary) and i.data["event_id"] == event_id
][0]
ts_lineup_entries = [
i
for i in ts_bulkload
if isinstance(i, EventLineupEntry) and i.data["event_id"] == event_id
]
ts_members = [i for i in ts_bulkload if isinstance(i, Member)]
ts_member_lookup = {m.data['id']: m for m in ts_members}
# ts_availability_lookup = {m.data['member_id']: m for m in ts_availabilities}
ts_lineup_entries_lookup = {m.data['member_id']: m for m in ts_lineup_entries}
return render(
request,
"event/view_event.html",
context={
"availability_summary": ts_availability_summary,
"event": ts_event,
"availablities": [],
"lineup_entries": ts_lineup_entries,
},
)
members = []
return render(request, "teamsnap/event/view_event.html", context={
"availability_summary":ts_availability_summary,
"event":ts_event,
"availablities":[],
"lineup_entries": ts_lineup_entries,
})
@login_required()
def location(request, id, team_id):
TOKEN = request.user.profile.teamsnap_access_token
from pyteamsnap.api import TeamSnap, Location
client = TeamSnap(token=TOKEN)
return render(request, "teamsnap/location/view.html", context={"location": Location.get(client, id=id)})
pass
@login_required()
def opponent(request, team_id, id):
TOKEN = request.user.profile.teamsnap_access_token
from pyteamsnap.api import TeamSnap, Opponent
time.sleep(0.5)
client = TeamSnap(token=TOKEN)
return render(request, "teamsnap/opponent.html", context={"opponent": Opponent.get(client, id=id)})
pass
@login_required()
def edit_lineup(request, event_ids, team_id):
TOKEN = request.user.profile.teamsnap_access_token
from django.forms import formset_factory
from teamsnap.forms import EventChooseForm
from pyteamsnap.api import TeamSnap, Event, Availability, Member, EventLineupEntry, EventLineup, AvailabilitySummary, Opponent
client = TeamSnap(token=TOKEN)
time.sleep(0.5)
import re
from teamsnap.forms import LineupEntryFormset
from pyteamsnap.api import (
Availability,
AvailabilitySummary,
Event,
EventLineup,
EventLineupEntry,
Member,
TeamSnap,
)
client = get_teamsnap_client(request)
event_ids = str(event_ids).split(",")
ts_bulkload = client.bulk_load(team_id=team_id,
types=[Event, EventLineup, EventLineupEntry, AvailabilitySummary, Member],
event__id=",".join(event_ids))
ts_bulkload = client.bulk_load(
team_id=team_id,
types=[Event, EventLineup, EventLineupEntry, AvailabilitySummary, Member],
event__id=",".join(event_ids),
)
event_ids = [int(i) for i in event_ids]
formsets_lineup = []
formsets_bench = []
formsets = []
events = []
contexts = []
for event_id in event_ids:
ts_event = [i for i in ts_bulkload if isinstance(i, Event) and i.data['id']==event_id][0]
ts_availabilities = Availability.search(client, event_id=ts_event.data['id'])
ts_availability_summary = \
[i for i in ts_bulkload if isinstance(i, AvailabilitySummary) and i.data['event_id'] == event_id][0]
ts_event = [
i for i in ts_bulkload if isinstance(i, Event) and i.data["id"] == event_id
][0]
ts_availabilities = Availability.search(client, event_id=ts_event.data["id"])
ts_lineup_entries = EventLineupEntry.search(client, event_id=event_id)
if ts_lineup_entries:
ts_lineup = EventLineup.get(client, id=ts_lineup_entries[0].data['event_lineup_id'])
else:
ts_lineup = EventLineup.search(client, event_id=event_id)
ts_members = [i for i in ts_bulkload if isinstance(i, Member)]
ts_member_lookup = {m.data['id']: m for m in ts_members}
ts_availability_lookup = {m.data['member_id']: m for m in ts_availabilities}
ts_lineup_entries_lookup = {m.data['member_id']: m for m in ts_lineup_entries}
ts_member_lookup = {m.data["id"]: m for m in ts_members}
ts_availability_lookup = {m.data["member_id"]: m for m in ts_availabilities}
ts_lineup_entries_lookup = {m.data["member_id"]: m for m in ts_lineup_entries}
members=[]
members = []
for member in ts_members:
members.append ({
"member":getattr(member, 'data'),
"availability": getattr(ts_availability_lookup.get(member.data['id'], {}), 'data', {}),
"lineup_entry": getattr(ts_lineup_entries_lookup.get(member.data['id'], {}), 'data', {})
}
for lineup_entry in ts_lineup_entries:
members.append(
{
"member": getattr(ts_member_lookup[lineup_entry.data['member_id']],'data'),
"availability": getattr(
ts_availability_lookup.get(lineup_entry.data['member_id'], {}), "data", {}
),
"lineup_entry": getattr(
lineup_entry, "data", {}
),
}
)
members = sorted(members, key=lambda d: (
{
None:3, # No Response
0:2, # No
2:1, # Maybe
1:0 # Yes
in_lineup_already = [m['member'] for m in members]
}.get(d['availability'].get('status_code')),
d['member'].get('last_name'))
)
from teamsnap.forms import LineupEntryFormset, LineupEntryForm
for member in ts_members:
if not member.data in in_lineup_already:
members.append(
{
"member": getattr(member, "data"),
"availability": getattr(
ts_availability_lookup.get(member.data["id"], {}), "data", {}
),
"lineup_entry": getattr(
ts_lineup_entries_lookup.get(member.data["id"], {}), "data", {}
),
}
)
members = sorted(
members,
key=lambda d: (
{None: 3, 0: 2, 2: 1, 1: 0}.get( # No Response # No # Maybe # Yes
d["availability"].get("status_code")
),
d["member"].get("last_name"),
),
)
initial = []
for member in members:
if not member['member']['is_non_player']:
initial_member = {}
if re.search(r'([A-Z0-9]+)(?:\s+\[(.*)\])?', member['lineup_entry'].get('label','')):
position, position_note = re.search(r'([A-Z0-9]+)(?:\s+\[(.*)\])?', member['lineup_entry'].get('label','')).groups()
if not member["member"]["is_non_player"]:
if re.search(
r"([A-Z0-9]+)(?:\s+\[(.*)\])?",
member["lineup_entry"].get("label", ""),
):
position, position_note = re.search(
r"([A-Z0-9]+)(?:\s+\[(.*)\])?",
member["lineup_entry"].get("label", ""),
).groups()
else:
position, position_note = ("","")
position, position_note = ("", "")
position_only = position_note == "PO"
initial.append({
"event_lineup_entry_id": member['lineup_entry'].get('id'),
"event_lineup_id": member['lineup_entry'].get('event_lineup_id'),
"event_id": event_id,
"position_only": position_only,
"member_id": member['member']['id'],
"sequence": member['lineup_entry'].get('sequence'),
"label": position,
}
initial.append(
{
"event_lineup_entry_id": member["lineup_entry"].get("id"),
"event_lineup_id": member["lineup_entry"].get(
"event_lineup_id"
),
"event_id": event_id,
"position_only": position_only,
"member_id": member["member"]["id"],
"sequence": member["lineup_entry"].get("sequence"),
"label": position,
}
)
formset = LineupEntryFormset(
initial=initial
)
formset = LineupEntryFormset(initial=initial)
for form in formset:
form.member = ts_member_lookup.get(form['member_id'].initial)
form.availability = ts_availability_lookup.get(form['member_id'].initial)
form.member = ts_member_lookup.get(form["member_id"].initial)
form.availability = ts_availability_lookup.get(form["member_id"].initial)
formset_startinglineup = [form for form in formset if form.initial.get('event_lineup_entry_id') and not form.initial.get('position_only')]
formset_startinglineup = [
form
for form in formset
if form.initial.get("event_lineup_entry_id")
and not form.initial.get("position_only")
]
formset_startinglineup = sorted(
formset_startinglineup,
key=lambda d: d.initial.get('sequence',100)
formset_startinglineup, key=lambda d: d.initial.get("sequence", 100)
)
formset_startingpositiononly = [form for form in formset if
form.initial.get('event_lineup_entry_id') and form not in formset_startinglineup]
formset_startingpositiononly = [
form
for form in formset
if form.initial.get("event_lineup_entry_id")
and form not in formset_startinglineup
]
formset_startingpositiononly = sorted(
formset_startingpositiononly,
key=lambda d: d.initial.get('sequence', 100)
formset_startingpositiononly, key=lambda d: d.initial.get("sequence", 100)
)
formset_bench = [form for form in formset if
form not in formset_startinglineup and
form not in formset_startingpositiononly and
form.availability.data['status_code'] in [2, 1]
]
formset_out = [form for form in formset if
form not in formset_startinglineup and
form not in formset_bench and
form not in formset_startingpositiononly and
not form.member.data['is_non_player']
]
formset_bench = [
form
for form in formset
if form not in formset_startinglineup
and form not in formset_startingpositiononly
and form.availability.data["status_code"] in [2, 1]
]
formset_out = [
form
for form in formset
if form not in formset_startinglineup
and form not in formset_bench
and form not in formset_startingpositiononly
and not form.member.data["is_non_player"]
]
contexts.append({
"event":ts_event,
"formset": formset,
"formset_bench":formset_bench,
"formset_startinglineup":formset_startinglineup,
"formset_startingpositionalonly":formset_startingpositiononly,
"formset_out":formset_out
})
contexts.append(
{
"event": ts_event,
"formset": formset,
"formset_bench": formset_bench,
"formset_startinglineup": formset_startinglineup,
"formset_startingpositionalonly": formset_startingpositiononly,
"formset_out": formset_out,
}
)
return render(
request, "lineup/multiple_edit.html", context={"contexts": contexts}
)
def dashboard(request, team_id=None):
if not team_id:
return redirect(
"teamsnap_dashboard", team_id=request.user.preferences.managed_team_id
)
from pyteamsnap.api import AvailabilitySummary, Event
client = get_teamsnap_client(request)
ts_events = Event.search(client, team_id=team_id)
ts_availability_summaries_d = {
a.data["id"]: a for a in AvailabilitySummary.search(client, team_id=team_id)
}
ts_events_future = [
e
for e in ts_events
if e.data["start_date"] > datetime.datetime.now(datetime.timezone.utc)
]
ts_events_past = [
e
for e in reversed(ts_events)
if e.data["start_date"] < datetime.datetime.now(datetime.timezone.utc)
]
return render(
request,
"dashboard.html",
{
"ts_events_future": ts_events_future,
"ts_events_past": ts_events_past,
"events_availabilities": [
(e, ts_availability_summaries_d[e.data["id"]]) for e in ts_events_future
],
},
)
return render(request, "teamsnap/lineup/multiple_edit.html", context={
"team_id": team_id,
"contexts":contexts
})
@login_required()
def submit_lineup(request, team_id, event_id):
from pyteamsnap.api import TeamSnap, EventLineupEntry, EventLineup
from teamsnap.forms import LineupEntryFormset
TOKEN = request.user.profile.teamsnap_access_token
client = TeamSnap(token=TOKEN)
time.sleep(0.5)
from pyteamsnap.api import EventLineup, TeamSnap, EventLineupEntry, Event
client = get_teamsnap_client(request)
ts_event = Event.get(client,event_id)
ts_lineup = EventLineup.search(client, event_id=event_id)
event_lineup_id = ts_lineup[0].data['id']
if request.GET:
@@ -408,157 +438,14 @@ def submit_lineup(request, team_id, event_id):
pass
# breakpoint()
pass
return HttpResponse(f'{r}')
return JsonResponse(ts_event.data)
pass
return HttpResponse(f'{team_id} {event_id}')
@login_required()
def image_generator(request, team_id, event_id):
TOKEN = request.user.profile.teamsnap_access_token
from pyteamsnap.api import TeamSnap, Event, Availability, Member, EventLineupEntry, EventLineup, AvailabilitySummary
client = TeamSnap(token=TOKEN)
time.sleep(0.5)
ts_event = Event.get(client, id=event_id)
return render(request, "teamsnap/event/instagen.html", context = {"event":ts_event})
@login_required()
def get_matchup_image(request, team_id, event_id, dimensions=None, background=None):
from pyteamsnap.api import TeamSnap, EventLineupEntry, EventLineup, Event, Team, Opponent, Location
from .utils.gen_image import Team as ImagegenTeam, Location as ImagegenLocation
from .utils.gen_image import gen_image, gen_results_image
import io
TOKEN = request.user.profile.teamsnap_access_token
if request.GET:
POSTPONED = request.GET.get('postponed', 'false') == 'true'
INCLUDE_WINLOSS = request.GET.get('winloss', 'false') == 'true'
BACKGROUND = request.GET.get('background', 'location')
game_id = event_id
dimensions = request.GET.get('dimensions')
width = int(dimensions.split("x")[0])
height = int(dimensions.split("x")[1])
teamsnap = TeamSnap(TOKEN)
time.sleep(0.5)
ts_event = Event.get(teamsnap, game_id).data
fave_team = Team.get(teamsnap, ts_event['team_id']).data
opponent_team = Opponent.get(teamsnap, ts_event['opponent_id']).data
location = Location.get(teamsnap, ts_event['location_id']).data
formatted_results = ts_event['formatted_results']
if formatted_results:
# L 4-3
runs_for = formatted_results.split(" ")[1].split("-")[0]
runs_against = formatted_results.split(" ")[1].split("-")[1]
else:
runs_for, runs_against = None, None
logo_image_directory = 'benchcoachproject/static/teamsnap/ig/logos-bw/{filename}.{ext}'
venue_image_directory = 'benchcoachproject/static/teamsnap/ig/locations/{filename}.{ext}'
def shortname_from_name(name):
return name.replace(" ", "").lower()
# date = parser.parse(ts_event['start_date'])
# date = date.astimezone(ZoneInfo("America/Chicago"))
game_info = {
"date": ts_event['start_date'],
"team_fave": ImagegenTeam(
name=fave_team["name"],
image_directory=logo_image_directory.format(filename=shortname_from_name(fave_team["name"]), ext="png")
),
"team_opponent": ImagegenTeam(
name=opponent_team["name"],
image_directory=logo_image_directory.format(filename=shortname_from_name(opponent_team["name"]),
ext="png")
),
"location": ImagegenLocation(
name=location['name'],
image_directory=venue_image_directory.format(filename=shortname_from_name(location["name"]), ext="png"),
# address=location['address']
),
"runs_for": runs_for,
"runs_against": runs_against
}
if not game_info['runs_for'] and not game_info['runs_against']:
image = gen_image(**game_info, background=BACKGROUND, width=width, height=height)
elif game_info['runs_for'] and game_info['runs_against']:
image = gen_results_image(**game_info, background=BACKGROUND, width=width, height=height)
else:
raise Exception
imgByteArr = io.BytesIO()
image.save(imgByteArr, format='PNG')
imgByteArr = imgByteArr.getvalue()
return HttpResponse(imgByteArr, content_type="image/png")
@login_required()
def multi_lineup_choose(request, team_id):
TOKEN = request.user.profile.teamsnap_access_token
from teamsnap.forms import EventChooseForm
from django.forms import formset_factory
from pyteamsnap.api import TeamSnap, Event
client = TeamSnap(token=TOKEN)
if request.POST:
ts_events = Event.search(client, team_id=team_id)
EventChooseFormset = formset_factory(EventChooseForm)
formset = EventChooseFormset(request.POST)
choices = [(e.data['id'], e.data['formatted_title']) for e in ts_events]
for form in formset:
form.fields['event_id'].choices = choices
if formset.is_valid():
event_ids = [f.cleaned_data['event_id'] for f in formset]
else:
event_ids = request.GET.get("event_ids").split(",")
EventChooseFormset = formset_factory(EventChooseForm)
formset = EventChooseFormset(request.POST)
return redirect('teamsnap_edit_multiple_lineups',team_id=team_id, event_ids=",".join(event_ids))
elif not request.GET.get('num'):
return HttpResponse(500)
else:
num = int(request.GET.get('num'))
TEAM_ID = team_id
TOKEN = request.user.profile.teamsnap_access_token
no_past = bool(request.GET.get('no_past', 0))
games_only = bool(request.GET.get('games_only', 0))
from pyteamsnap.api import TeamSnap, Event, Location, Opponent
client = TeamSnap(token=TOKEN)
time.sleep(.5)
ts_events = Event.search(client, team_id=TEAM_ID)
if no_past:
ts_events = [e for e in ts_events if e.data['start_date'] > datetime.datetime.now(datetime.timezone.utc)]
if games_only:
ts_events = [e for e in ts_events if e.data['is_game']]
ts_events = {e.data['id']: e for e in ts_events}
# ts_opponents = {o.data['id']:o for o in Opponent.search(client, team_id=TEAM_ID)}
# ts_locations = {l.data['id']:l for l in Location.search(client, team_id=TEAM_ID)}
# for event in ts_events:
EventChooseFormset = formset_factory(EventChooseForm, extra=num)
formset = EventChooseFormset()
choices= [(id, e.data['formatted_title']) for id, e in ts_events.items()]
for form in formset:
form.fields['event_id'].choices = choices
pass
return render(request, "teamsnap/lineup/multiple_choose.html", context={"formset": formset, "team_id": team_id})
return HttpResponseServerError
def gamecard(request, team_id, event_id):
TOKEN = request.user.profile.teamsnap_access_token
from django.forms import formset_factory
from teamsnap.forms import EventChooseForm
from pyteamsnap.api import TeamSnap, Event, Availability, Member, EventLineupEntry, EventLineup, \
AvailabilitySummary, Opponent
client = TeamSnap(token=TOKEN)
time.sleep(0.5)
import re
from pyteamsnap.api import Event, Availability, Member, EventLineupEntry, EventLineup, \
AvailabilitySummary
client = get_teamsnap_client(request)
ts_bulkload = client.bulk_load(team_id=team_id,
types=[Event, EventLineup, EventLineupEntry, AvailabilitySummary, Member],
event__id=event_id)
@@ -642,4 +529,4 @@ def gamecard(request, team_id, event_id):
"members_startinglineup":members_startinglineup,
"members_startingpositiononly":members_startingpositiononly
}
return render(request, "teamsnap/lineup/gamecard.html", context=context)
return render(request, "lineup/gamecard.html", context=context)