Merge branch 'teamsnap_sync' into dev
This commit is contained in:
@@ -36,6 +36,7 @@ INSTALLED_APPS = [
|
|||||||
'venues.apps.VenuesConfig',
|
'venues.apps.VenuesConfig',
|
||||||
'players.apps.PlayersConfig',
|
'players.apps.PlayersConfig',
|
||||||
'lineups.apps.LineupsConfig',
|
'lineups.apps.LineupsConfig',
|
||||||
|
'teamsnap.apps.TeamsnapConfig',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
|
|||||||
@@ -27,5 +27,6 @@ urlpatterns = [
|
|||||||
path('teams/', include('teams.urls')),
|
path('teams/', include('teams.urls')),
|
||||||
path('venues/', include('venues.urls')),
|
path('venues/', include('venues.urls')),
|
||||||
path('players/', include('players.urls')),
|
path('players/', include('players.urls')),
|
||||||
path('lineups/', include('lineups.urls'))
|
path('lineups/', include('lineups.urls')),
|
||||||
|
path('teamsnap/', include('teamsnap.urls'))
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ from django.http import HttpResponse
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
def welcome(request):
|
def welcome(request):
|
||||||
pages = ['events list', 'teams list', 'venues list', 'players list']
|
pages = ['events list', 'teams list', 'venues list', 'players list', 'teamsnap list events']
|
||||||
return render(request,'home.html',{'pages':pages})
|
return render(request,'home.html',{'pages':pages})
|
||||||
18
lineups/migrations/0007_alter_positioning_position.py
Normal file
18
lineups/migrations/0007_alter_positioning_position.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 16:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('lineups', '0006_alter_positioning_order'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='positioning',
|
||||||
|
name='position',
|
||||||
|
field=models.CharField(blank=True, choices=[('EH', 'EH'), ('P', 'P'), ('C', 'C'), ('1B', '1B'), ('2B', '2B'), ('3B', '3B'), ('SS', 'SS'), ('LF', 'LF'), ('CF', 'CF'), ('RF', 'RF'), ('DH', 'DH')], default=None, max_length=2, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
8
reload_teamsnap.sh
Normal file
8
reload_teamsnap.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
PROJECT_PATH="/Users/asc/PycharmProjects_Local/benchcoach"
|
||||||
|
PYTHON_PATH="$PROJECT_PATH/venv/bin/python"
|
||||||
|
FIXTURES="2021cmba"
|
||||||
|
MANAGE_PY_PATH="$PROJECT_PATH/manage.py"
|
||||||
|
bash -cl "$PYTHON_PATH $MANAGE_PY_PATH migrate teamsnap zero"
|
||||||
|
bash -cl "$PYTHON_PATH $MANAGE_PY_PATH migrate teamsnap"
|
||||||
|
bash -cl "$PYTHON_PATH $PROJECT_PATH/teamsnap/scripts/import_teamsnap.py"
|
||||||
0
teamsnap/__init__.py
Normal file
0
teamsnap/__init__.py
Normal file
10
teamsnap/admin.py
Normal file
10
teamsnap/admin.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import User, Team, Location, Event, Member, Availability
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
admin.site.register(User)
|
||||||
|
admin.site.register(Team)
|
||||||
|
admin.site.register(Event)
|
||||||
|
admin.site.register(Location)
|
||||||
|
admin.site.register(Member)
|
||||||
|
admin.site.register(Availability)
|
||||||
6
teamsnap/apps.py
Normal file
6
teamsnap/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TeamsnapConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'teamsnap'
|
||||||
21
teamsnap/forms.py
Normal file
21
teamsnap/forms.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from django import forms
|
||||||
|
from .models import LineupEntry
|
||||||
|
from events.models import Event
|
||||||
|
from players.models import Player
|
||||||
|
from django.forms import modelformset_factory, inlineformset_factory, BaseModelFormSet,formset_factory
|
||||||
|
from crispy_forms.helper import FormHelper, Layout
|
||||||
|
|
||||||
|
class LineupEntryForm(forms.ModelForm):
|
||||||
|
availability = None
|
||||||
|
class Meta:
|
||||||
|
model = LineupEntry
|
||||||
|
widgets = {
|
||||||
|
'label': forms.Select(attrs={'class': 'form-control form-control-sm'})
|
||||||
|
}
|
||||||
|
exclude = ()
|
||||||
|
|
||||||
|
LineupEntryFormSet = modelformset_factory(
|
||||||
|
model=LineupEntry,
|
||||||
|
form=LineupEntryForm,
|
||||||
|
extra=0
|
||||||
|
)
|
||||||
82
teamsnap/migrations/0001_initial.py
Normal file
82
teamsnap/migrations/0001_initial.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-20 23:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0001_initial'),
|
||||||
|
('players', '0003_player_team'),
|
||||||
|
('venues', '0001_initial'),
|
||||||
|
('events', '0004_delete_availability'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='User',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('teamsnap_id', models.CharField(max_length=10)),
|
||||||
|
('access_token', models.CharField(max_length=50)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Team',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('teamsnap_id', models.CharField(max_length=10)),
|
||||||
|
('name', models.CharField(max_length=50, null=True)),
|
||||||
|
('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teams.team')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Member',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('teamsnap_id', models.CharField(max_length=10)),
|
||||||
|
('name', models.CharField(max_length=50, null=True)),
|
||||||
|
('player', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='players.player')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Location',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('teamsnap_id', models.CharField(max_length=10)),
|
||||||
|
('name', models.CharField(max_length=50, null=True)),
|
||||||
|
('venue', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='venues.venue')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Event',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('teamsnap_id', models.CharField(max_length=10)),
|
||||||
|
('name', models.CharField(max_length=50, null=True)),
|
||||||
|
('label', models.CharField(max_length=50, null=True)),
|
||||||
|
('start_date', models.DateTimeField(null=True)),
|
||||||
|
('event', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='events.event')),
|
||||||
|
('location', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.location')),
|
||||||
|
('opponent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
43
teamsnap/migrations/0002_auto_20211121_0035.py
Normal file
43
teamsnap/migrations/0002_auto_20211121_0035.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 00:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='formatted_title',
|
||||||
|
field=models.CharField(max_length=50, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='teamsnap_id',
|
||||||
|
field=models.CharField(max_length=10, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='teamsnap_id',
|
||||||
|
field=models.CharField(max_length=10, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='member',
|
||||||
|
name='teamsnap_id',
|
||||||
|
field=models.CharField(max_length=10, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='team',
|
||||||
|
name='teamsnap_id',
|
||||||
|
field=models.CharField(max_length=10, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='teamsnap_id',
|
||||||
|
field=models.CharField(max_length=10, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
60
teamsnap/migrations/0003_auto_20211121_1540.py
Normal file
60
teamsnap/migrations/0003_auto_20211121_1540.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 15:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0002_auto_20211121_0035'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='event',
|
||||||
|
old_name='event',
|
||||||
|
new_name='bencoach_event',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='location',
|
||||||
|
old_name='venue',
|
||||||
|
new_name='bencoach_venue',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='member',
|
||||||
|
old_name='player',
|
||||||
|
new_name='bencoach_player',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='team',
|
||||||
|
old_name='team',
|
||||||
|
new_name='bencoach_team',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='member',
|
||||||
|
name='first_name',
|
||||||
|
field=models.CharField(max_length=50, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='member',
|
||||||
|
name='is_non_player',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='member',
|
||||||
|
name='jersey_number',
|
||||||
|
field=models.IntegerField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='member',
|
||||||
|
name='last_name',
|
||||||
|
field=models.CharField(max_length=50, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='member',
|
||||||
|
name='team',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team'),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
teamsnap/migrations/0004_remove_member_name.py
Normal file
17
teamsnap/migrations/0004_remove_member_name.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 15:44
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0003_auto_20211121_1540'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='member',
|
||||||
|
name='name',
|
||||||
|
),
|
||||||
|
]
|
||||||
30
teamsnap/migrations/0005_availability.py
Normal file
30
teamsnap/migrations/0005_availability.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 16:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('lineups', '0007_alter_positioning_position'),
|
||||||
|
('teamsnap', '0004_remove_member_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Availability',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('teamsnap_id', models.CharField(max_length=10, unique=True)),
|
||||||
|
('status_code', models.SmallIntegerField(null=True)),
|
||||||
|
('benchcoach_availability', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='lineups.availability')),
|
||||||
|
('event', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.event')),
|
||||||
|
('member', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.member')),
|
||||||
|
('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
teamsnap/migrations/0006_alter_availability_status_code.py
Normal file
18
teamsnap/migrations/0006_alter_availability_status_code.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 16:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0005_availability'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='availability',
|
||||||
|
name='status_code',
|
||||||
|
field=models.SmallIntegerField(choices=[(1, 'Yes'), (0, 'No'), (2, 'Maybe'), (None, 'Unknown')], default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
24
teamsnap/migrations/0007_auto_20211121_1628.py
Normal file
24
teamsnap/migrations/0007_auto_20211121_1628.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 16:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0006_alter_availability_status_code'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='team',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='teamsnap.team'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='opponent',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opponent', to='teamsnap.team'),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
teamsnap/migrations/0008_alter_availability_options.py
Normal file
17
teamsnap/migrations/0008_alter_availability_options.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 16:36
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0007_auto_20211121_1628'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='availability',
|
||||||
|
options={'verbose_name_plural': 'availabilities'},
|
||||||
|
),
|
||||||
|
]
|
||||||
23
teamsnap/migrations/0009_auto_20211121_1757.py
Normal file
23
teamsnap/migrations/0009_auto_20211121_1757.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 17:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0008_alter_availability_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='points_for_opponent',
|
||||||
|
field=models.PositiveSmallIntegerField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='points_for_team',
|
||||||
|
field=models.PositiveSmallIntegerField(null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
teamsnap/migrations/0010_event_is_game.py
Normal file
19
teamsnap/migrations/0010_event_is_game.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 18:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0009_auto_20211121_1757'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='is_game',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
27
teamsnap/migrations/0011_lineupentry.py
Normal file
27
teamsnap/migrations/0011_lineupentry.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 18:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0010_event_is_game'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LineupEntry',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('label', models.PositiveSmallIntegerField(blank=True, choices=[(11, 'EH'), (1, 'P'), (2, 'C'), (3, '1B'), (4, '2B'), (5, '3B'), (6, 'SS'), (7, 'LF'), (8, 'CF'), (9, 'RF'), (10, 'DH')], default=None, null=True)),
|
||||||
|
('sequence', models.PositiveSmallIntegerField(default=0, null=True)),
|
||||||
|
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teamsnap.event')),
|
||||||
|
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teamsnap.member')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('member', 'event')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
23
teamsnap/migrations/0012_auto_20211121_2010.py
Normal file
23
teamsnap/migrations/0012_auto_20211121_2010.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 20:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0011_lineupentry'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='lineupentry',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=50, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='lineupentry',
|
||||||
|
name='teamsnap_id',
|
||||||
|
field=models.CharField(max_length=10, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
teamsnap/migrations/0013_remove_lineupentry_name.py
Normal file
17
teamsnap/migrations/0013_remove_lineupentry_name.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 20:10
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0012_auto_20211121_2010'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='lineupentry',
|
||||||
|
name='name',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
teamsnap/migrations/0014_alter_lineupentry_teamsnap_id.py
Normal file
18
teamsnap/migrations/0014_alter_lineupentry_teamsnap_id.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-11-21 20:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teamsnap', '0013_remove_lineupentry_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='lineupentry',
|
||||||
|
name='teamsnap_id',
|
||||||
|
field=models.CharField(blank=True, max_length=10, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
teamsnap/migrations/__init__.py
Normal file
0
teamsnap/migrations/__init__.py
Normal file
133
teamsnap/models.py
Normal file
133
teamsnap/models.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
import lineups.models
|
||||||
|
import teams.models
|
||||||
|
import venues.models
|
||||||
|
import players.models
|
||||||
|
import events.models
|
||||||
|
|
||||||
|
class TeamsnapBaseModel(models.Model):
|
||||||
|
teamsnap_id = models.CharField(max_length=10, unique=True)
|
||||||
|
name = models.CharField(max_length=50, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} ({self.teamsnap_id})"
|
||||||
|
|
||||||
|
class User(TeamsnapBaseModel):
|
||||||
|
access_token = models.CharField(max_length = 50)
|
||||||
|
name = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.teamsnap_id}"
|
||||||
|
|
||||||
|
class Team(TeamsnapBaseModel):
|
||||||
|
bencoach_team = models.ForeignKey(teams.models.Team, null=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def view_url(self):
|
||||||
|
return f"https://go.teamsnap.com/{self.team.teamsnap_id}/team/view/{self.teamsnap_id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edit_url(self):
|
||||||
|
return f"https://go.teamsnap.com/{self.team.teamsnap_id}/team/edit/{self.teamsnap_id}"
|
||||||
|
|
||||||
|
class Location(TeamsnapBaseModel):
|
||||||
|
bencoach_venue = models.ForeignKey(venues.models.Venue, null=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def view_url(self):
|
||||||
|
return f"https://go.teamsnap.com/{self.team.teamsnap_id}/location/view/{self.teamsnap_id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edit_url(self):
|
||||||
|
return f"https://go.teamsnap.com/{self.team.teamsnap_id}/location/edit/{self.teamsnap_id}"
|
||||||
|
|
||||||
|
class Member(TeamsnapBaseModel):
|
||||||
|
name = None
|
||||||
|
bencoach_player = models.ForeignKey(players.models.Player, null=True, on_delete=models.CASCADE)
|
||||||
|
team = models.ForeignKey(Team, null=True, on_delete=models.CASCADE)
|
||||||
|
first_name = models.CharField(max_length = 50, null=True)
|
||||||
|
last_name = models.CharField(max_length = 50, null=True)
|
||||||
|
jersey_number = models.IntegerField(null=True)
|
||||||
|
is_non_player = models.BooleanField()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.last_name}, {self.first_name} ({self.teamsnap_id})"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def view_url(self):
|
||||||
|
return f"https://go.teamsnap.com/{self.team.teamsnap_id}/roster/player/{self.teamsnap_id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edit_url(self):
|
||||||
|
return f"https://go.teamsnap.com/{self.team.teamsnap_id}/roster/edit/{self.teamsnap_id}"
|
||||||
|
|
||||||
|
class Event(TeamsnapBaseModel):
|
||||||
|
bencoach_event = models.ForeignKey(events.models.Event, null=True, on_delete=models.CASCADE)
|
||||||
|
label = models.CharField(max_length = 50, null=True)
|
||||||
|
start_date = models.DateTimeField(null=True)
|
||||||
|
opponent = models.ForeignKey(Team, null=True, on_delete=models.CASCADE, related_name="opponent")
|
||||||
|
team = models.ForeignKey(Team, null=True, on_delete=models.CASCADE)
|
||||||
|
location = models.ForeignKey(Location, null=True, on_delete=models.CASCADE)
|
||||||
|
formatted_title = models.CharField(max_length = 50, null=True)
|
||||||
|
points_for_opponent = models.PositiveSmallIntegerField(null=True)
|
||||||
|
points_for_team = models.PositiveSmallIntegerField(null=True)
|
||||||
|
is_game = models.BooleanField()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def view_url(self):
|
||||||
|
return f"https://go.teamsnap.com/{self.team.teamsnap_id}/schedule/view_game/{self.teamsnap_id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edit_url(self):
|
||||||
|
return f"https://go.teamsnap.com/{self.team.teamsnap_id}/schedule/edit_game/{self.teamsnap_id}"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.formatted_title} ({self.teamsnap_id})"
|
||||||
|
|
||||||
|
class Availability(TeamsnapBaseModel):
|
||||||
|
status_codes = [
|
||||||
|
(1, 'Yes'),
|
||||||
|
(0, 'No'),
|
||||||
|
(2, 'Maybe'),
|
||||||
|
(None, 'Unknown')
|
||||||
|
]
|
||||||
|
name = None
|
||||||
|
team = models.ForeignKey(Team, null=True, on_delete=models.CASCADE)
|
||||||
|
event = models.ForeignKey(Event, null=True, on_delete=models.CASCADE)
|
||||||
|
member = models.ForeignKey(Member, null=True, on_delete=models.CASCADE)
|
||||||
|
benchcoach_availability = models.ForeignKey(lineups.models.Availability, null=True, on_delete=models.CASCADE)
|
||||||
|
status_code = models.SmallIntegerField(null=True, choices=status_codes, default=None)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.member} - {self.event} ({self.teamsnap_id})"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "availabilities"
|
||||||
|
|
||||||
|
class LineupEntry(TeamsnapBaseModel):
|
||||||
|
name = None
|
||||||
|
teamsnap_id = models.CharField(max_length=10, unique=True, null=True, blank=True)
|
||||||
|
member = models.ForeignKey(Member, on_delete=models.CASCADE)
|
||||||
|
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||||
|
positions = [
|
||||||
|
(11, 'EH'),
|
||||||
|
(1, 'P'),
|
||||||
|
(2, 'C'),
|
||||||
|
(3, '1B'),
|
||||||
|
(4, '2B'),
|
||||||
|
(5, '3B'),
|
||||||
|
(6, 'SS'),
|
||||||
|
(7, 'LF'),
|
||||||
|
(8, 'CF'),
|
||||||
|
(9, 'RF'),
|
||||||
|
(10,'DH')
|
||||||
|
]
|
||||||
|
label = models.PositiveSmallIntegerField(choices=positions, default=None, null=True, blank=True)
|
||||||
|
sequence = models.PositiveSmallIntegerField(default=0, null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('member', 'event',)
|
||||||
3
teamsnap/teamsnap/__init__.py
Normal file
3
teamsnap/teamsnap/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .api import TeamSnap
|
||||||
|
|
||||||
|
__all__ = ['TeamSnap']
|
||||||
118
teamsnap/teamsnap/api.py
Normal file
118
teamsnap/teamsnap/api.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
__all__ = ['TeamSnap', 'Team', 'Event', 'Availability', 'Member', 'Location', 'Me']
|
||||||
|
from apiclient import APIClient, HeaderAuthentication, JsonResponseHandler
|
||||||
|
|
||||||
|
|
||||||
|
class ApiObject():
|
||||||
|
rel = None
|
||||||
|
|
||||||
|
def __init__(self, client, rel=rel, data={}):
|
||||||
|
self.client = client
|
||||||
|
self.data = data
|
||||||
|
self.rel = rel
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def search(cls, client, **kwargs):
|
||||||
|
results = client.query(cls.rel, "search", **kwargs)
|
||||||
|
return [cls(client,rel=cls.rel, data=r) for r in results]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, client, id):
|
||||||
|
r = client.get(f"{client.link(cls.rel)}/{id}")
|
||||||
|
return cls(client, cls.rel, client.parse_response(r)[0])
|
||||||
|
|
||||||
|
class Me (ApiObject):
|
||||||
|
rel = "me"
|
||||||
|
|
||||||
|
def __init__(self, client):
|
||||||
|
super().__init__(client=client, rel=self.rel, data=client.get(client.link(self.rel)))
|
||||||
|
|
||||||
|
class Event (ApiObject):
|
||||||
|
rel = "events"
|
||||||
|
|
||||||
|
class Team (ApiObject):
|
||||||
|
rel = "teams"
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Availability (ApiObject):
|
||||||
|
rel = "availabilities"
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Member (ApiObject):
|
||||||
|
rel = "members"
|
||||||
|
|
||||||
|
class Location (ApiObject):
|
||||||
|
rel = "locations"
|
||||||
|
|
||||||
|
class Opponent (ApiObject):
|
||||||
|
rel = "opponents"
|
||||||
|
|
||||||
|
class EventLineupEntry (ApiObject):
|
||||||
|
rel = "event_lineup_entries"
|
||||||
|
|
||||||
|
class Statistics (ApiObject):
|
||||||
|
rel = "statistics"
|
||||||
|
|
||||||
|
class MemberStatistics (ApiObject):
|
||||||
|
rel = "member_statistics"
|
||||||
|
|
||||||
|
class TeamSnap(APIClient):
|
||||||
|
base_url = 'https://api.teamsnap.com/v3'
|
||||||
|
|
||||||
|
def __init__(self, token, *args, **kwargs):
|
||||||
|
super().__init__(*args,
|
||||||
|
authentication_method=HeaderAuthentication(token=token),
|
||||||
|
response_handler=JsonResponseHandler,
|
||||||
|
**kwargs)
|
||||||
|
self._root_collection = self.get(self.base_url)['collection']
|
||||||
|
self._links = self._by_rel(self.base_url, 'links')
|
||||||
|
self._queries = self._by_rel(self.base_url, 'queries')
|
||||||
|
self._commands = self._by_rel(self.base_url, 'commands')
|
||||||
|
pass
|
||||||
|
|
||||||
|
def link(self, link_name):
|
||||||
|
d = {l['rel']:l['href'] for l in self._root_collection["links"]}
|
||||||
|
return d.get(link_name)
|
||||||
|
|
||||||
|
def _by_rel (self, url, k):
|
||||||
|
try:
|
||||||
|
{l['rel']: l for l in self._root_collection[k]}
|
||||||
|
except Exception as e:
|
||||||
|
return {}
|
||||||
|
self.get(url)['collection'][k]
|
||||||
|
return {l['rel']:l for l in self.get(url)['collection'][k]}
|
||||||
|
|
||||||
|
def query (self, rel, query, **kwargs):
|
||||||
|
queries = self._by_rel(self._get_href(rel), 'queries')
|
||||||
|
response = self.get(self._get_href(query, queries), params=kwargs)
|
||||||
|
return self.parse_response(response)
|
||||||
|
|
||||||
|
def command (self, rel, command, **kwargs):
|
||||||
|
commands = self._by_rel(self._get_href(rel), 'commands')
|
||||||
|
response = self.get(self._get_href(command, commands), params=kwargs)
|
||||||
|
return self.parse_response(response)
|
||||||
|
|
||||||
|
def _get_href (self, rel: str, links:dict = None, url = base_url) -> str:
|
||||||
|
"""returns a hyperlink from a the links dictionary. Each item in the links dictionary is a
|
||||||
|
dictionary with a rel and href key"""
|
||||||
|
if links is None: links = self._by_rel(url, 'links')
|
||||||
|
link = links[rel]['href']
|
||||||
|
return link
|
||||||
|
|
||||||
|
def get_item (self, rel, id):
|
||||||
|
r = self.get(f"{self.link(rel)}/{id}")
|
||||||
|
return self.parse_response(r)[0]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_response(self, response):
|
||||||
|
result = []
|
||||||
|
items = [item['data'] for item in response['collection'].get('items',[])]
|
||||||
|
for item in response['collection'].get('items',[]):
|
||||||
|
details = {}
|
||||||
|
for detail in item['data']:
|
||||||
|
# TODO type casting and validation based on item['type']
|
||||||
|
details[detail['name']] = detail['value']
|
||||||
|
result.append(details)
|
||||||
|
|
||||||
|
return result
|
||||||
|
# return [{detail['name']: detail['value'] for detail in item} for item in items]
|
||||||
|
|
||||||
23
teamsnap/templates/teamsnap/event_list.html
Normal file
23
teamsnap/templates/teamsnap/event_list.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %} {{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
{#<ol class="list-group">#}
|
||||||
|
{% for item in object_list %}
|
||||||
|
<ul class="card">
|
||||||
|
<span class="card-title">{{ item.formatted_title }}</span>
|
||||||
|
<span class="fs-6">{{ item.subtitle }}</span>
|
||||||
|
{# {% if item.body %}#}
|
||||||
|
{# <br><span class="fs-6">{{ item.body }}</span>#}
|
||||||
|
<br>{{ item.start_date|date:"D, M j, g:i A" }},<br>{{item.location.name}}
|
||||||
|
{# {% endif %}#}
|
||||||
|
<br>
|
||||||
|
{% for button in item.buttons %}
|
||||||
|
<a class="btn btn-primary btn-sm" href="{{ button.href }}" role="button">{{ button.label }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
{% endblock %}
|
||||||
157
teamsnap/templates/teamsnap/lineup.html
Normal file
157
teamsnap/templates/teamsnap/lineup.html
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
{% extends 'base.html' %}{% block title %} {{ title }} {% endblock %}{% load crispy_forms_tags %}{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="w-100 text-center mx-auto text-center">
|
||||||
|
<h1 class="display-5 fw-bold"><a href="#" class="btn btn-primary btn-secondary p-1 mx-3"><</a>{{ event.formatted_title }}<a href="#" class="btn btn-primary btn-secondary p-1 mx-3">></a></h1>
|
||||||
|
<div class="lead">
|
||||||
|
<p class="">{{ event.start_date|date:"l, F j, Y g:i A" }}<br>{{ event.location.name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<form action="{% url 'teamsnap edit lineup' event_id=event.id%}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ formset.management_form }}
|
||||||
|
<div class="row">
|
||||||
|
{# <input type="submit" value="Submit" class="btn btn-sm btn-success mx-3 my-0 my-0">#}
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card my-1">
|
||||||
|
<div class="card-header"><h5>Lineup</h5></div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
{% include 'teamsnap/player-table.html' with table_id="lineup" formset=formset_lineup available_class="d-none"%}
|
||||||
|
<div class="justify-content-md-end d-md-flex m-2"><input type="submit" value="Submit" class="btn btn-primary"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card my-1">
|
||||||
|
<div class="card-header"><h5>DH'd</h5></div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
{% include 'teamsnap/player-table.html' with table_id="dhd" formset=formset_dhd available_class="d-none" sequence_class="d-none"%}
|
||||||
|
<div class="justify-content-md-end d-md-flex m-2"><input type="submit" value="Submit" class="btn btn-primary"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card my-1">
|
||||||
|
<div class="card-header"><h5>Bench</h5></div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
{% include 'teamsnap/player-table.html' with table_id="bench" formset=formset_bench sequence_class="d-none"%}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script src="{% static 'js/Sortable.js' %}"></script>
|
||||||
|
<script id="sortable">
|
||||||
|
function refresh_lineup_sequence (){
|
||||||
|
var member_rows = document.getElementById('lineup').querySelectorAll('tr')
|
||||||
|
var has_dh = false
|
||||||
|
|
||||||
|
for (let i = 0; i < member_rows.length; i++) {
|
||||||
|
if (member_rows[i].dataset.sequence == 0) {
|
||||||
|
has_dh = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (has_dh) {
|
||||||
|
member_rows[i].dataset.sequence = i
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
member_rows[i].dataset.sequence = i+1
|
||||||
|
}
|
||||||
|
var member_sequence = member_rows[i].querySelector('[id^="member-sequence-button"]')
|
||||||
|
var form_element_sequence = member_rows[i].querySelector('[id$="sequence"]')
|
||||||
|
member_sequence.innerText = parseInt(member_rows[i].dataset.sequence)
|
||||||
|
form_element_sequence.value = parseInt(member_rows[i].dataset.sequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var lineup = new Sortable.create(
|
||||||
|
document.getElementById('dhd'), {
|
||||||
|
animation: 150,
|
||||||
|
ghostClass: "ghost",
|
||||||
|
{#handle: ".bars-move",#}
|
||||||
|
group: {
|
||||||
|
put: true,
|
||||||
|
pull: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var lineup = new Sortable.create(
|
||||||
|
document.getElementById('lineup'), {
|
||||||
|
animation: 150,
|
||||||
|
ghostClass:"ghost",
|
||||||
|
{#handle: ".bars-move",#}
|
||||||
|
group:{
|
||||||
|
put:true,
|
||||||
|
pull:true
|
||||||
|
},
|
||||||
|
onAdd: function (/**Event*/evt) {
|
||||||
|
// Add to Lineup
|
||||||
|
var itemEl = evt.item; // dragged HTMLElement
|
||||||
|
var member_id = itemEl.dataset.memberId
|
||||||
|
console.log(itemEl)
|
||||||
|
var form_element_sequence =itemEl.querySelector('[id$="sequence"]')
|
||||||
|
var member_sequence = itemEl.querySelector('[id^="member-sequence"]')
|
||||||
|
var member_available =itemEl.querySelector('[id^="member-available"]')
|
||||||
|
var member_sequence_button =itemEl.querySelector('[id^="member-sequence-button"]')
|
||||||
|
console.log(member_sequence.parentElement.dataset)
|
||||||
|
toggle_in_lineup(member_sequence_button)
|
||||||
|
member_sequence.parentElement.dataset.sequence = evt.newIndex
|
||||||
|
refresh_lineup_sequence()
|
||||||
|
{#member_available.parentElement.style.display="none"#}
|
||||||
|
member_available.parentElement.classList.add('d-none')
|
||||||
|
{#member_sequence.style.display="table-cell"#}
|
||||||
|
member_sequence.classList.remove('d-none')
|
||||||
|
},
|
||||||
|
onUpdate: function (/**Event*/evt) {
|
||||||
|
console.log('update to lineup')
|
||||||
|
var itemEl = evt.item; // dragged HTMLElement
|
||||||
|
refresh_lineup_sequence()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
var bench = new Sortable.create(
|
||||||
|
document.getElementById('bench'), {
|
||||||
|
animation: 150,
|
||||||
|
ghostClass:"ghost",
|
||||||
|
sort: false,
|
||||||
|
{#handle: ".bars-move",#}
|
||||||
|
group:{
|
||||||
|
put:true,
|
||||||
|
pull:true
|
||||||
|
},
|
||||||
|
onAdd: function (/**Event*/evt) {
|
||||||
|
console.log('added to bench')
|
||||||
|
var itemEl = evt.item; // dragged HTMLElement
|
||||||
|
console.log(itemEl)
|
||||||
|
var form_element_sequence =itemEl.querySelector('[id$="sequence"]')
|
||||||
|
var member_sequence = itemEl.querySelector('[id^="member-sequence"]')
|
||||||
|
var member_available =itemEl.querySelector('[id^="member-available"]')
|
||||||
|
{#member_available.parentElement.style.display="table-cell"#}
|
||||||
|
member_available.parentElement.classList.remove('d-none')
|
||||||
|
form_element_sequence.value = 0
|
||||||
|
member_sequence.innerHTML = 1
|
||||||
|
{#member_sequence.style.display="none"#}
|
||||||
|
member_sequence.classList.add('d-none')
|
||||||
|
var member_id = itemEl.dataset.memberId
|
||||||
|
refresh_lineup_sequence()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function toggle_in_lineup(sequence_button){
|
||||||
|
var member_row = sequence_button.parentNode.parentNode
|
||||||
|
if (member_row.dataset.sequence == 0) {
|
||||||
|
sequence_button.innerText = "1"
|
||||||
|
sequence_button.classList.add("btn-light")
|
||||||
|
sequence_button.classList.remove("btn-dark")
|
||||||
|
member_row.dataset.sequence = 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sequence_button.innerText = "D"
|
||||||
|
sequence_button.classList.remove("btn-light")
|
||||||
|
sequence_button.classList.add("btn-dark")
|
||||||
|
member_row.dataset.sequence = 0
|
||||||
|
}
|
||||||
|
refresh_lineup_sequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
86
teamsnap/templates/teamsnap/player-table.html
Normal file
86
teamsnap/templates/teamsnap/player-table.html
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr class="border border-light">
|
||||||
|
{# <th scope="col" style="display: none"></th>#}
|
||||||
|
{# <th scope="col" class="border border-light"></th>#}
|
||||||
|
{# <th scope="col">Name</th>#}
|
||||||
|
{# <th scope="col">Pos</th>#}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id={{ table_id }}>
|
||||||
|
{% for form in formset %}
|
||||||
|
<tr data-member-id="{{ form.instance.member.id }}"
|
||||||
|
data-position="{{ form.instance.position }}"
|
||||||
|
data-sequence="{{ form.instance.sequence }}">
|
||||||
|
{{ form.id.as_hidden }}
|
||||||
|
{{ form.event.as_hidden }}
|
||||||
|
{{ form.sequence.as_hidden }}
|
||||||
|
{{ form.member.as_hidden }}
|
||||||
|
{{ form.teamsnap_id.as_hidden }}
|
||||||
|
<td id="member-availability-{{ form.instance.member.id }}"
|
||||||
|
class="{{ available_class }}"
|
||||||
|
>
|
||||||
|
{% if form.availability.status_code == 2 %}
|
||||||
|
<button class="btn btn-light bg-info p-1"
|
||||||
|
id="member-available-{{ form.instance.member.id }}"
|
||||||
|
>
|
||||||
|
<span style="visibility: hidden">2</span>
|
||||||
|
<span class="visually-hidden">Maybe</span>
|
||||||
|
</button>
|
||||||
|
{% elif form.availability.status_code == 1%}
|
||||||
|
<button class="btn btn-light bg-success p-1"
|
||||||
|
id="member-available-{{ form.instance.member.id }}"
|
||||||
|
>
|
||||||
|
<span style="visibility: hidden">1</span>
|
||||||
|
</button>
|
||||||
|
<span class="visually-hidden">Maybe</span>
|
||||||
|
{% elif form.availability.status_code == 0%}
|
||||||
|
<button class="btn btn-light bg-danger p-1"
|
||||||
|
id="member-available-{{ form.instance.member.id }}"
|
||||||
|
>
|
||||||
|
<span style="visibility: hidden">0</span>
|
||||||
|
</button>
|
||||||
|
<span class="visually-hidden">No</span>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-light bg-secondary p-1"
|
||||||
|
id="member-available-{{ form.instance.member.id }}"
|
||||||
|
>
|
||||||
|
<span style="visibility: hidden">X</span>
|
||||||
|
</button>
|
||||||
|
<span class="visually-hidden">Unknown</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<th scope="row"
|
||||||
|
id="member-sequence-{{ form.instance.member.id }}"
|
||||||
|
class="{{ sequence_class }}">
|
||||||
|
{% if form.sequence.value %}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-light p-1"
|
||||||
|
id="member-sequence-button-{{ form.instance.member.id }}"
|
||||||
|
onclick="toggle_in_lineup(this)"
|
||||||
|
>
|
||||||
|
{{ form.sequence.value }}
|
||||||
|
</button>
|
||||||
|
{% elif form.sequence.value == 0 %}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-dark p-1"s
|
||||||
|
id="member-sequence-button-{{ form.instance.member.id }}"
|
||||||
|
onclick="toggle_in_lineup(this)"
|
||||||
|
>
|
||||||
|
D
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{{ form.instance.member.first_name }} {{ form.instance.member.last_name }}
|
||||||
|
<small class="text-muted fw-light">#{{ form.instance.member.jersey_number }}</small>
|
||||||
|
{# <br><code><small>{{ form.statline }}</small></code>#}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
{{ form.label }}
|
||||||
|
</td>
|
||||||
|
{# <td>{{ form.instance.position }}</td>#}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
3
teamsnap/tests.py
Normal file
3
teamsnap/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
12
teamsnap/urls.py
Normal file
12
teamsnap/urls.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from django.urls import path, include
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('events', views.EventsListView.as_view(), name="teamsnap list events"),
|
||||||
|
path('edit/event/<int:id>', views.edit_event, name='teamsnap edit event'),
|
||||||
|
path('edit/lineup/<int:event_id>', views.edit_lineup, name='teamsnap edit lineup')
|
||||||
|
]
|
||||||
114
teamsnap/views.py
Normal file
114
teamsnap/views.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
from django.shortcuts import render, redirect
|
||||||
|
|
||||||
|
# from .teamsnap.api import TeamSnap, Team, Event, Availability
|
||||||
|
from .models import User, Member, Team, Event, Location, LineupEntry
|
||||||
|
from django.views.generic.list import ListView
|
||||||
|
from lib.views import BenchcoachListView
|
||||||
|
from .forms import LineupEntryForm, LineupEntryFormSet
|
||||||
|
from django.forms.models import model_to_dict
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.db.models import Case, When
|
||||||
|
|
||||||
|
def queryset_from_ids(Model, id_list):
|
||||||
|
#https://stackoverflow.com/questions/4916851/django-get-a-queryset-from-array-of-ids-in-specific-order
|
||||||
|
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(id_list)])
|
||||||
|
queryset = Model.objects.filter(pk__in=id_list).order_by(preserved)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def edit_event(request, id):
|
||||||
|
event = Event.objects.get(id = id)
|
||||||
|
return redirect(event.edit_url)
|
||||||
|
|
||||||
|
class EventsListView(BenchcoachListView):
|
||||||
|
Model = Event
|
||||||
|
edit_url = 'teamsnap edit event'
|
||||||
|
list_url = 'teamsnap list events'
|
||||||
|
page_title = "TeamSnap Events"
|
||||||
|
title_strf = '{item.formatted_title}'
|
||||||
|
body_strf = "{item.start_date:%a, %b %-d, %-I:%M %p},\n{item.location.name}"
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
context = super().get_context_data()
|
||||||
|
for item in context['items']:
|
||||||
|
item['buttons'].append(
|
||||||
|
{
|
||||||
|
'label': 'Edit Lineup',
|
||||||
|
'href': reverse('teamsnap edit lineup', args=[item['id']])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
|
||||||
|
class TeamListView(BenchcoachListView):
|
||||||
|
Model = Team
|
||||||
|
edit_url = 'teamsnap edit team'
|
||||||
|
list_url = 'teamsnap list teams'
|
||||||
|
page_title = "TeamSnap Teams"
|
||||||
|
|
||||||
|
class LocationListView(BenchcoachListView):
|
||||||
|
Model = Location
|
||||||
|
edit_url = 'teamsnap edit location'
|
||||||
|
list_url = 'teamsnap list locations'
|
||||||
|
page_title = "TeamSnap Locations"
|
||||||
|
|
||||||
|
def edit_lineup(request, event_id):
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
# create a form instance and populate it with data from the request:
|
||||||
|
formset = LineupEntryFormSet(request.POST)
|
||||||
|
for form in formset:
|
||||||
|
if form.is_valid():
|
||||||
|
# process the data in form.cleaned_data as required
|
||||||
|
# ...
|
||||||
|
# redirect to a new URL:
|
||||||
|
|
||||||
|
if isinstance(form.cleaned_data['id'], LineupEntry):
|
||||||
|
positioning_id = form.cleaned_data.pop('id').id #FIXME this is a workaround, not sure why it is necessary
|
||||||
|
positioning = LineupEntry.objects.filter(id=positioning_id)
|
||||||
|
positioning.update(**form.cleaned_data)
|
||||||
|
did_create = False
|
||||||
|
else:
|
||||||
|
positioning = LineupEntry.objects.create(**form.cleaned_data, event_id=event_id)
|
||||||
|
did_create = True
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
return render(request, 'success.html', {'call_back':'teamsnap edit lineup','id':event_id, 'errors':[error for error in formset.errors if error]}, status=200)
|
||||||
|
# return render(request, 'success.html', {'call_back':'schedule'})
|
||||||
|
event = Event.objects.get(id=event_id)
|
||||||
|
members = Member.objects.filter(is_non_player=False).prefetch_related('availability_set', 'lineupentry_set')
|
||||||
|
# players_d.sort(key=lambda d: (-d['availability'].available, d['last_name']))
|
||||||
|
|
||||||
|
for member in members:
|
||||||
|
LineupEntry.objects.get_or_create(member_id=member.id, event_id=event_id)
|
||||||
|
|
||||||
|
qs_starting_lineup = LineupEntry.objects.filter(event_id=event_id, sequence__isnull=False, sequence__gt=0).order_by('sequence')
|
||||||
|
qs_bench = LineupEntry.objects.filter(event_id=event_id, sequence=0).prefetch_related('member__availability_set').order_by('member__last_name')
|
||||||
|
|
||||||
|
# This is all a compromise to get the sorting just the way I wanted. THERE'S GOT TO BE A BETTER WAY
|
||||||
|
ids_starting_lineup = [item.id for item in qs_starting_lineup]
|
||||||
|
ids_bench_available = [item.id for item in qs_bench
|
||||||
|
if item.member.availability_set.get(event_id=event_id).status_code == 1]
|
||||||
|
ids_bench_maybe = [item.id for item in qs_bench
|
||||||
|
if item.member.availability_set.get(event_id=event_id).status_code == 2]
|
||||||
|
ids_bench_no = [item.id for item in qs_bench
|
||||||
|
if item.member.availability_set.get(event_id=event_id).status_code == 0]
|
||||||
|
ids_bench_unknown = [item.id for item in qs_bench
|
||||||
|
if item.member.availability_set.get(event_id=event_id).status_code is None]
|
||||||
|
qset = queryset_from_ids(LineupEntry, ids_starting_lineup + ids_bench_available + ids_bench_maybe + ids_bench_no + ids_bench_unknown)
|
||||||
|
|
||||||
|
formset = LineupEntryFormSet(queryset=qset)
|
||||||
|
|
||||||
|
for f in formset:
|
||||||
|
if f.instance.member_id:
|
||||||
|
f.availability = f.instance.member.availability_set.get(event_id=event_id)
|
||||||
|
# f.statline = f.instance.member.statline_set.get()
|
||||||
|
|
||||||
|
formset_lineup = [f for f in formset if f.instance.sequence]
|
||||||
|
formset_bench = [f for f in formset if f not in formset_lineup]
|
||||||
|
formset_dhd = [f for f in formset if not f.instance.sequence and f.instance.label]
|
||||||
|
|
||||||
|
return render(request, 'teamsnap/lineup.html', {'title': 'Lineup',
|
||||||
|
'event': event,
|
||||||
|
'formset_lineup': formset_lineup,
|
||||||
|
'formset_bench':formset_bench,
|
||||||
|
'formset_dhd':formset_dhd
|
||||||
|
})
|
||||||
@@ -9,9 +9,16 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<span class="text-success">Success!</span> <span class="text-muted">Redirecting...</span>
|
{% if errors %}
|
||||||
|
<span class="text-danger">Errors...</span>
|
||||||
{% for error in errors %}
|
{% for error in errors %}
|
||||||
error
|
error
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<span class="text-success">Success!</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="text-muted">Redirecting...</span>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user