first init of this template.

This commit is contained in:
2022-05-14 07:16:31 -05:00
parent d6ee55a050
commit 4632926a34
40 changed files with 1854 additions and 857 deletions

View File

@@ -242,7 +242,7 @@ class TestVenueViews(TestCase):
fixtures = ["blaseball"]
def test_venue_list(self):
response = self.client.get(reverse("venues list"))
response = self.client.get(reverse("locations list"))
self.assertEqual(response.status_code, 200)
self.assertIn(
{"id": 1, "title": "Chesapeake Racetrack and Ballpark"},

View File

@@ -8,7 +8,7 @@ urlpatterns = [
path('events/<int:event_id>/', login_required(views.event), name="event"),
path('players/list/', login_required(views.PlayerListView.as_view()), name="player list"),
path('teams/list/', login_required(views.TeamListView.as_view()), name="team list"),
path('venues/list/', login_required(views.VenueListView.as_view()), name="venue list"),
path('locations/list/', login_required(views.VenueListView.as_view()), name="venue list"),
path('events/<int:event_id>/card', login_required(views.lineupcard), name="lineup card"),
path('events/<int:event_id>/csv', views.csv_export, name="lineup csv"),
path('events/<int:event_id>/<str:active_tab>', login_required(views.event), name="event")

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/static/mstile-150x150.png"/>
<TileColor>#323669</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M3438 6215 c-2 -1 -59 -5 -128 -9 -69 -3 -136 -7 -150 -10 -14 -2
-47 -7 -75 -10 -394 -45 -765 -221 -900 -428 -61 -92 -81 -110 -181 -158 -60
-29 -95 -54 -133 -96 -50 -55 -190 -259 -280 -409 -39 -65 -192 -317 -396
-650 -47 -77 -135 -216 -195 -310 -60 -93 -112 -176 -116 -183 -4 -8 -34 -56
-68 -107 -34 -52 -115 -176 -181 -277 -99 -153 -318 -493 -362 -564 -44 -70
-147 -250 -174 -304 -31 -59 -34 -73 -33 -150 0 -98 23 -165 89 -265 44 -66
187 -214 280 -291 33 -27 62 -52 65 -55 3 -3 73 -57 155 -119 433 -328 650
-537 768 -740 14 -25 46 -94 71 -154 50 -121 64 -141 94 -132 88 28 293 365
376 617 34 105 40 131 52 224 4 28 7 51 9 53 1 1 10 -39 19 -90 39 -205 152
-362 290 -402 30 -9 112 -55 195 -109 l142 -94 747 3 c411 1 776 5 812 8 97 8
147 27 256 95 55 34 140 82 189 106 50 23 109 59 132 79 61 53 108 155 137
296 l23 115 12 -57 c6 -32 16 -84 22 -115 54 -276 345 -764 435 -729 16 6 45
68 76 164 69 214 291 461 665 738 276 205 354 266 445 352 225 214 318 376
313 543 -2 70 -11 90 -119 267 -95 157 -186 299 -451 702 -282 429 -473 732
-803 1275 -303 499 -392 632 -466 698 -30 26 -144 87 -165 87 -20 0 -73 55
-88 91 -40 101 -343 322 -525 383 -64 21 -232 63 -293 72 -27 5 -52 9 -55 9
-3 1 -32 5 -65 9 -33 5 -73 10 -90 12 -16 3 -100 7 -185 10 -85 3 -163 7 -172
8 -9 2 -18 3 -20 1z m272 -361 c41 -8 92 -19 113 -25 142 -38 280 -156 263
-224 -3 -13 -24 -61 -46 -106 -23 -50 -35 -88 -30 -93 5 -5 27 -4 54 4 86 24
79 -1 -19 -64 -133 -87 -258 -249 -332 -431 -38 -93 -32 -111 25 -72 21 15 41
27 44 27 4 0 -11 -51 -31 -112 -21 -62 -44 -144 -50 -183 -12 -73 -20 -202
-13 -209 2 -3 51 18 108 46 57 28 107 48 110 45 10 -9 -15 -43 -90 -122 -67
-71 -81 -93 -47 -76 51 28 421 160 450 161 15 0 -38 -40 -132 -101 -281 -181
-420 -281 -407 -291 191 -146 660 -634 788 -820 30 -44 55 -114 46 -129 -3 -5
-20 -6 -39 -3 -42 6 -55 -13 -55 -81 0 -121 56 -296 171 -533 91 -188 110
-243 114 -329 7 -133 -104 -466 -169 -508 -35 -23 -117 -16 -154 14 -65 51
-181 102 -451 196 -91 31 -196 71 -234 88 l-70 31 6 61 c8 87 46 167 95 203
22 16 91 50 154 77 179 77 248 128 283 212 32 76 14 130 -70 221 -164 178
-316 234 -615 226 -63 -1 -137 -8 -165 -14 -27 -7 -61 -14 -75 -16 -47 -9
-155 -57 -217 -96 -111 -70 -175 -161 -179 -255 -6 -122 35 -168 238 -270 204
-102 282 -180 295 -293 4 -36 2 -40 -34 -63 -46 -29 -134 -64 -307 -124 -249
-85 -406 -152 -406 -172 0 -16 -68 -41 -113 -41 -74 0 -96 30 -158 207 -52
149 -67 221 -67 317 0 105 29 206 92 316 115 201 169 353 180 506 7 86 -7 109
-64 109 -54 0 -55 6 -4 109 33 65 64 110 139 195 158 181 491 517 597 602 63
50 75 65 66 76 -17 21 -113 92 -198 147 -205 134 -330 217 -330 222 0 7 57 -4
85 -16 11 -4 61 -22 110 -40 50 -17 99 -36 110 -41 11 -5 38 -15 60 -23 22 -8
58 -22 80 -31 l40 -17 -20 23 c-11 13 -41 46 -67 74 -62 67 -88 101 -82 108
10 9 63 -11 140 -52 42 -23 78 -41 81 -41 8 0 -4 176 -16 235 -5 28 -26 99
-45 159 -20 60 -36 110 -36 112 0 10 18 2 36 -15 11 -10 31 -22 44 -26 22 -7
23 -5 16 26 -9 46 -78 185 -135 272 -59 89 -134 164 -223 223 -99 66 -104 85
-18 64 28 -7 54 -9 57 -6 4 3 -12 48 -35 99 -53 116 -54 133 -14 183 69 86
168 134 347 169 87 17 335 16 425 -1z m-1377 -1583 c78 -31 138 -63 144 -76 8
-22 -23 -43 -74 -51 -24 -3 -43 -12 -43 -18 0 -20 62 -74 105 -92 50 -21 136
-21 194 0 43 16 108 58 123 80 5 8 8 8 8 0 0 -17 -60 -107 -88 -133 -108 -96
-284 -79 -363 36 -53 76 -107 231 -93 267 6 15 23 12 87 -13z m2410 -3 c6 -20
-20 -143 -39 -180 -65 -126 -190 -192 -307 -162 -82 21 -131 65 -182 163 l-17
35 43 -35 c56 -44 78 -55 131 -65 87 -15 167 2 222 48 53 43 46 66 -25 88 -31
9 -61 20 -68 24 -18 12 4 33 47 46 20 7 51 18 67 25 49 21 59 23 92 24 17 1
33 -5 36 -11z m383 -978 c12 -149 7 -382 -11 -510 -2 -14 -6 -52 -10 -85 -4
-33 -9 -62 -10 -65 -2 -3 -6 -34 -10 -70 -4 -36 -9 -67 -12 -70 -2 -3 -12 5
-21 17 -9 12 -50 60 -90 107 l-73 85 45 138 c25 76 75 232 112 346 36 115 68
207 69 205 2 -2 7 -46 11 -98z m-3208 -22 c16 -46 65 -191 107 -322 88 -271
88 -241 -2 -343 -103 -117 -113 -127 -114 -112 0 8 -2 23 -4 34 -2 11 -7 45
-10 75 -4 30 -8 69 -10 85 -2 17 -7 59 -10 95 -4 36 -8 81 -11 100 -5 41 -4
359 1 410 2 19 4 41 4 48 2 32 22 4 49 -70z m1752 -452 c81 -9 181 -39 240
-72 l45 -25 -40 7 c-83 15 -129 11 -176 -13 -66 -34 -225 -46 -377 -29 -62 7
-83 13 -132 41 -25 14 -100 14 -165 0 -27 -5 -27 -4 10 16 134 74 351 101 595
75z m265 -1150 c138 -8 161 -12 169 -33 10 -25 -11 -49 -92 -111 -91 -68 -151
-99 -207 -108 -80 -12 -488 -11 -579 3 -96 14 -135 32 -244 114 -81 61 -107
93 -88 108 19 16 117 27 268 30 73 1 134 4 135 5 5 4 524 -2 638 -8z"/>
<path d="M3065 3166 c-23 -23 -26 -60 -9 -88 17 -27 121 21 131 60 12 51 -78
72 -122 28z"/>
<path d="M3453 3149 c-57 -21 -47 -89 16 -111 49 -16 144 -16 165 1 33 28 9
76 -49 99 -41 16 -105 21 -132 11z"/>
<path d="M3837 3141 c-37 -18 -44 -38 -23 -66 20 -27 95 -28 125 -1 26 24 27
41 1 63 -26 22 -63 24 -103 4z"/>
<path d="M2788 2934 c-28 -8 -57 -26 -72 -45 -29 -35 -32 -51 -12 -68 30 -25
141 28 176 83 22 35 -24 51 -92 30z"/>
<path d="M4140 2860 c-27 -50 33 -118 126 -144 14 -4 27 1 39 14 17 19 17 21
1 53 -23 42 -96 97 -129 97 -16 0 -30 -8 -37 -20z"/>
<path d="M2579 2469 c-43 -43 11 -177 83 -202 44 -15 78 61 56 125 -9 26 -61
87 -76 89 -4 0 -15 2 -25 4 -10 2 -27 -5 -38 -16z"/>
<path d="M4333 2428 c-54 -14 -109 -111 -98 -171 5 -27 50 -67 75 -67 50 0 99
89 94 174 -2 53 -29 76 -71 64z"/>
<path d="M3933 2113 c-34 -7 -74 -59 -68 -91 5 -28 37 -61 61 -63 8 -1 20 -2
27 -3 21 -4 66 23 83 49 13 20 14 30 4 59 -6 20 -15 38 -19 40 -13 8 -65 13
-88 9z"/>
<path d="M3033 2083 c-34 -7 -42 -51 -13 -75 26 -21 109 -25 128 -6 40 40 -39
95 -115 81z"/>
<path d="M2765 1984 c-6 -1 -27 -5 -49 -9 -48 -8 -86 -45 -86 -83 0 -35 12
-42 71 -36 89 8 127 43 120 110 -1 11 -37 23 -56 18z"/>
<path d="M4314 1938 c-12 -5 -27 -21 -33 -35 -16 -35 13 -83 51 -83 15 0 30 5
33 10 4 6 10 8 14 5 5 -2 21 7 36 21 21 20 26 30 20 52 -4 15 -12 30 -18 34
-17 11 -78 9 -103 -4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/static/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@@ -0,0 +1,137 @@
function positionSelectChanged(elem) {
let row = elem.parentElement.parentElement
let original_table = elem.parentElement.parentElement.parentElement
let lineup_table = document.getElementById('lineup')
let bench_table = document.getElementById('bench')
let dhd_table = document.getElementById('dhd')
let player_order = row.querySelector('[id^="player-order"]')
let player_available = row.querySelector('[id^="player-availability"]')
console.dir(original_table == bench_table)
if (elem.value == "P" && original_table == bench_table ) {
dhd_table.appendChild(row)
player_order.classList.remove('d-none')
// player_available.classList.add('d-none')
refresh_lineup_order()
}
else if (elem.value && original_table == dhd_table ) {
dhd_table.appendChild(row)
player_order.classList.remove('d-none')
// player_available.classList.add('d-none')
refresh_lineup_order()
}
else if (elem.value) {
lineup_table.appendChild(row)
player_order.classList.remove('d-none')
// player_available.classList.add('d-none')
refresh_lineup_order()
}
else {
bench_table.prepend(row)
// player_order.classList.add('d-none')
player_available.classList.remove('d-none')
}
}
function colorPositions(){
var player_rows = [];
player_rows.push.apply(player_rows, document.getElementById('table-players-lineup').querySelectorAll('tr'));
player_rows.push.apply(player_rows, document.getElementById('table-players-bench').querySelectorAll('tr'));
var label_value_array = []
player_rows.forEach(function (player_row, index){
console.dir(player_row)
if (player_row.querySelector('[name$="label"]')){
console.dir(player_row.querySelector('select[name$="label"]'))
console.dir(player_row.querySelector('select[name$="label"]').value)
label_value_array.push(
player_row.querySelector('select[name$="label"]').value)
}
}
)
document.querySelectorAll('[id^="position-status"]').forEach(function(position_status,index){
if (label_value_array.includes(position_status.innerHTML)){
if (position_status.classList.contains("text-danger")){
position_status.classList.remove('text-danger')
}
position_status.classList.add('text-success')
} else {
if (position_status.classList.contains("text-success")){
position_status.classList.remove('text-success')
}
position_status.classList.add('text-danger')
}
})
console.dir(label_value_array)
}
function refresh_lineup_order (){
var player_rows = document.getElementById('table-players-lineup').querySelectorAll('tr')
for (let i = 0; i < player_rows.length; i++) {
var player_order = player_rows[i].querySelector('[id^="sequence"]')
var form_element_order = player_rows[i].querySelector('[id$="sequence"]')
player_order.innerText = parseInt(player_rows[i].dataset.order)
player_rows[i].dataset.order = i
form_element_order.value = i
player_order.innerHTML = i+1
}
var player_rows = document.getElementById('table-players-bench').querySelectorAll('tr')
for (let i = 0; i < player_rows.length; i++) {
var player_order = player_rows[i].querySelector('[id^="player-order"]')
var form_element_order = player_rows[i].querySelector('[id$="sequence"]')
player_rows[i].dataset.order = null
form_element_order.value = null
player_order.innerHTML = null
}
}
var lineup = new Sortable.create(
document.getElementById('tbody-players-lineup'), {
animation: 150,
handle: ".drag-handle",
ghostClass:"ghost",
group:{
put:true,
pull:true
},
onAdd: function (/**Event*/evt) {
// Add to Lineup
var itemEl = evt.item; // dragged HTMLElement
var player_order = itemEl.querySelector('[id^="sequence-member"]')
var player_available =itemEl.querySelector('[class^="member-availability-status"]')
refresh_lineup_order()
if (player_order.classList.contains('d-none')){
player_order.classList.remove('d-none')
}
// player_available.classList.add('d-none')
},
onUpdate: function (/**Event*/evt) {
console.log('update to lineup')
var itemEl = evt.item; // dragged HTMLElement
refresh_lineup_order()
},
});
var bench = new Sortable.create(
document.getElementById('tbody-players-bench'), {
animation: 150,
ghostClass:"ghost",
sort: false,
handle: ".drag-handle",
// handle: ".bars-move",
group:{
put:true,
pull:true
},
onAdd: function (/**Event*/evt) {
var itemEl = evt.item; // dragged HTMLElement
var player_order = itemEl.querySelector('[id^="sequence-member"]')
var player_available =itemEl.querySelector('[class^="member-availability-status"]')
refresh_lineup_order()
// player_order.classList.add('d-none')
if (player_order.classList.contains('d-none')){
player_available.classList.remove('d-none')
}
}
});
//xxx
colorPositions()

View File

@@ -1,6 +1,6 @@
from django import forms
from .models import Team, Location, Opponent, Event, Member
from django.forms import modelformset_factory
from django.forms import modelformset_factory, formset_factory, inlineformset_factory
select_kwargs = {
'attrs':{'class': 'form-control form-control-sm'}
@@ -74,4 +74,45 @@ class LocationForm(forms.ModelForm):
"benchcoach_object": forms.Select(**select_kwargs)
}
class LineupEntryForm(forms.Form):
member = None
availability = None
lineup_entry = None
event_lineup_entry_id = forms.Field(required=False)
event_lineup_id = forms.Field(required=False)
event_id = forms.Field()
member_id = forms.Field()
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)

View File

@@ -1,7 +1,7 @@
from django.db import models
import benchcoach.models
import teamsnap.teamsnap.api
import pyteamsnap.api
from django.utils.timezone import localtime
class TeamsnapBaseModel(models.Model):
@@ -9,7 +9,7 @@ class TeamsnapBaseModel(models.Model):
id = models.CharField(max_length=50, unique=True, primary_key=True)
created_at = models.DateTimeField(null=True)
updated_at = models.DateTimeField(null=True)
ApiObject = teamsnap.teamsnap.api.ApiObject
ApiObject = pyteamsnap.api.ApiObject
class Meta:
abstract = True
@@ -29,7 +29,7 @@ class Team(TeamsnapBaseModel):
on_delete=models.CASCADE,
related_name="teamsnap_team"
)
ApiObject = teamsnap.teamsnap.api.Team
ApiObject = pyteamsnap.api.Team
class User(TeamsnapBaseModel):
type = 'user'
@@ -37,7 +37,7 @@ class User(TeamsnapBaseModel):
last_name = models.CharField(max_length = 50, null=True)
email = models.EmailField(null=True)
managed_teams = models.ManyToManyField(Team)
ApiObject = teamsnap.teamsnap.api.User
ApiObject = pyteamsnap.api.User
@classmethod
def update_or_create_from_teamsnap_api(cls, teamsnap_data):
@@ -76,7 +76,7 @@ class Opponent(TeamsnapManagedObjectModel):
on_delete=models.CASCADE,
related_name="teamsnap_opponent"
)
ApiObject = teamsnap.teamsnap.api.Opponent
ApiObject = pyteamsnap.api.Opponent
class Location(TeamsnapManagedObjectModel):
type = 'location'
@@ -86,7 +86,7 @@ class Location(TeamsnapManagedObjectModel):
on_delete=models.CASCADE,
related_name="teamsnap_location"
)
ApiObject = teamsnap.teamsnap.api.Location
ApiObject = pyteamsnap.api.Location
class Member(TeamsnapManagedObjectModel):
# url format is
@@ -103,7 +103,7 @@ class Member(TeamsnapManagedObjectModel):
last_name = models.CharField(max_length = 50, null=True)
jersey_number = models.IntegerField(null=True)
is_non_player = models.BooleanField(null=True)
ApiObject = teamsnap.teamsnap.api.Member
ApiObject = pyteamsnap.api.Member
def __str__(self):
return f"{self.last_name}, {self.first_name} ({self.id})"
@@ -131,7 +131,7 @@ class Event(TeamsnapManagedObjectModel):
points_for_team = models.PositiveSmallIntegerField(null=True)
is_game = models.BooleanField(null=True)
game_type = models.CharField(max_length = 50, null=True)
ApiObject = teamsnap.teamsnap.api.Event
ApiObject = pyteamsnap.api.Event
@property
def csv_event_title(self)->str:
@@ -166,7 +166,7 @@ class Availability(TeamsnapManagedObjectModel):
related_name="teamsnap_availability"
)
status_code = models.SmallIntegerField(choices=status_codes, null=True, blank=True, default=None)
ApiObject = teamsnap.teamsnap.api.Availability
ApiObject = pyteamsnap.api.Availability
def __str__(self):
return f"{self.member} - {self.event} ({self.id})"
@@ -197,7 +197,7 @@ class LineupEntry(TeamsnapManagedObjectModel):
)
label = models.PositiveSmallIntegerField(choices=positions, default=None, null=True, blank=True)
sequence = models.PositiveSmallIntegerField(default=0, null=True, blank=True)
ApiObject = teamsnap.teamsnap.api.EventLineupEntry
ApiObject = pyteamsnap.api.EventLineupEntry
@classmethod
def update_or_create_from_teamsnap_api(cls, teamsnap_data):

View File

@@ -1,3 +0,0 @@
# from .utils import TeamSnap
#
# __all__ = ['TeamSnap']

View File

@@ -1,618 +0,0 @@
__all__ = ['TeamSnap', 'Team', 'Event', 'Availability', 'Member', 'Location', 'Me']
from apiclient import APIClient, HeaderAuthentication, JsonResponseHandler
class ApiObject():
rel = None
version = None
template = 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"
version = "3.866.0"
template = {
"data": [
{
"name": "first_name"
},
{
"name": "last_name"
},
{
"name": "password"
},
{
"name": "birthday"
},
{
"name": "email"
},
{
"name": "facebook_id",
"deprecated": True,
"prompt": "facebook_id is deprecated and has been removed. Continued use of facebook_id is not recommended it will no longer be stored."
},
{
"name": "facebook_access_token",
"deprecated": True,
"prompt": "facebook_access_token is deprecated and has been removed. Continued use of facebook_access_token is not recommended it will no longer be stored."
},
{
"name": "type",
"value": "user"
},
{
"name": "is_lab_rat"
},
{
"name": "receives_newsletter"
}
]
}
def __init__(self, client):
super().__init__(client=client, rel=self.rel, data=client.get(client.link(self.rel)))
class User (ApiObject):
rel = "users"
version = "3.866.0"
template = {
"data": [
{
"name": "first_name"
},
{
"name": "last_name"
},
{
"name": "password"
},
{
"name": "birthday"
},
{
"name": "email"
},
{
"name": "facebook_id",
"deprecated": True,
"prompt": "facebook_id is deprecated and has been removed. Continued use of facebook_id is not recommended it will no longer be stored."
},
{
"name": "facebook_access_token",
"deprecated": True,
"prompt": "facebook_access_token is deprecated and has been removed. Continued use of facebook_access_token is not recommended it will no longer be stored."
},
{
"name": "type",
"value": "user"
},
{
"name": "is_lab_rat"
},
{
"name": "receives_newsletter"
}
]
}
class Event (ApiObject):
rel = "events"
version = "3.866.0"
template = {
"data": [
{
"name": "type",
"value": "event"
},
{
"name": "additional_location_details"
},
{
"name": "browser_time_zone"
},
{
"name": "division_location_id"
},
{
"name": "doesnt_count_towards_record"
},
{
"name": "duration_in_minutes"
},
{
"name": "game_type_code"
},
{
"name": "icon_color"
},
{
"name": "is_canceled"
},
{
"name": "is_game"
},
{
"name": "is_overtime"
},
{
"name": "is_shootout"
},
{
"name": "is_tbd"
},
{
"name": "label"
},
{
"name": "location_id"
},
{
"name": "minutes_to_arrive_early"
},
{
"name": "name"
},
{
"name": "notes"
},
{
"name": "notify_opponent"
},
{
"name": "notify_opponent_contacts_email"
},
{
"name": "notify_opponent_contacts_name"
},
{
"name": "notify_opponent_notes"
},
{
"name": "notify_team"
},
{
"name": "notify_team_as_member_id"
},
{
"name": "opponent_id"
},
{
"name": "points_for_opponent"
},
{
"name": "points_for_team"
},
{
"name": "repeating_include",
"prompt": "When updating a repeating event, this is a required field. Values are: \"all\" - updates all events in this series, \"future\" - updates this event and all that occur after, \"none\" - only updates a single event."
},
{
"name": "repeating_type_code",
"prompt": "A code for the frequency of the repeated event, this is required with the \"repeating_include\" attribute when creating a repeating event. Valid values are: \"1\" - repeat an event daily, \"2\" - repeat an event weekly."
},
{
"name": "repeating_until",
"prompt": "A date when the repeating event should end, this is inclusive so an event will be created on this day if it falls before the next event specified by \"repeating_type_code\". This attribute is required with \"repeating_type_code\" when creating a repeating event."
},
{
"name": "results"
},
{
"name": "results_url"
},
{
"name": "shootout_points_for_opponent"
},
{
"name": "shootout_points_for_team"
},
{
"name": "start_date"
},
{
"name": "team_id"
},
{
"name": "time_zone"
},
{
"name": "tracks_availability"
},
{
"name": "uniform"
}
]}
class Team (ApiObject):
rel = "teams"
version = "3.866.0"
template = {
"data": [
{
"name": "name"
},
{
"name": "location_country"
},
{
"name": "location_postal_code"
},
{
"name": "time_zone",
"prompt": "The time_zone parameter is required when creating a team, but for changing a team's time_zone, use the update_time_zone command"
},
{
"name": "sport_id"
},
{
"name": "division_id"
},
{
"name": "division_name"
},
{
"name": "season_name"
},
{
"name": "league_name"
},
{
"name": "league_url"
},
{
"name": "owner_first_name"
},
{
"name": "owner_last_name"
},
{
"name": "owner_email"
},
{
"name": "is_ownership_pending"
},
{
"name": "ad_unit_hero_id"
},
{
"name": "ad_unit_hero_template_id"
},
{
"name": "ad_unit_inline_id"
},
{
"name": "type",
"value": "team"
}
]
}
class Availability (ApiObject):
rel = "availabilities"
version = "3.866.0"
template = {
"data": [
{
"name": "status_code"
},
{
"name": "notes"
},
{
"name": "event_id"
},
{
"name": "member_id"
},
{
"name": "notes_author_member_id"
},
{
"name": "source"
},
{
"name": "type",
"value": "availability"
}
]
}
class Member (ApiObject):
rel = "members"
version = "3.866.0"
template = {
"data": [
{
"name": "first_name"
},
{
"name": "last_name"
},
{
"name": "gender"
},
{
"name": "position"
},
{
"name": "is_manager"
},
{
"name": "birthday"
},
{
"name": "hide_age",
"deprecated": True,
"prompt": "hide_age is deprecated and will be removed in a future version, use is_age_hidden instead."
},
{
"name": "is_age_hidden"
},
{
"name": "hide_address",
"deprecated": True,
"prompt": "hide_address is deprecated and will be removed in a future version, use is_address_hidden instead."
},
{
"name": "is_address_hidden"
},
{
"name": "is_non_player"
},
{
"name": "address_street1"
},
{
"name": "address_street2"
},
{
"name": "address_city"
},
{
"name": "address_state"
},
{
"name": "address_zip"
},
{
"name": "jersey_number"
},
{
"name": "team_id"
},
{
"name": "is_ownership_pending"
},
{
"name": "source_action"
},
{
"name": "type",
"value": "member"
}
]
},
class Location (ApiObject):
rel = "locations"
version = "3.866.0"
template = {
"data": [
{
"name": "name"
},
{
"name": "url"
},
{
"name": "phone"
},
{
"name": "notes"
},
{
"name": "address"
},
{
"name": "latitude"
},
{
"name": "longitude"
},
{
"name": "team_id"
},
{
"name": "is_retired"
},
{
"name": "type",
"value": "location"
}
]
}
class Opponent (ApiObject):
rel = "opponents"
version = "3.866.0"
template = {
"data": [
{
"name": "name"
},
{
"name": "contacts_name"
},
{
"name": "contacts_phone"
},
{
"name": "contacts_email"
},
{
"name": "notes"
},
{
"name": "team_id"
},
{
"name": "type",
"value": "opponent"
}
]
}
class EventLineupEntry (ApiObject):
rel = "event_lineup_entries"
version = "3.866.0"
template = {
"data": [
{
"name": "event_lineup_id"
},
{
"name": "member_id"
},
{
"name": "sequence"
},
{
"name": "label"
},
{
"name": "type",
"value": "event_lineup_entry"
}
]
}
class Statistics (ApiObject):
rel = "statistics"
version = "3.866.0"
template = {
"data": [
{
"name": "acronym"
},
{
"name": "always_display_decimals"
},
{
"name": "formula"
},
{
"name": "is_in_descending_order"
},
{
"name": "display_zero_totals"
},
{
"name": "is_percentage"
},
{
"name": "is_private"
},
{
"name": "is_team_statistic"
},
{
"name": "is_top_statistic"
},
{
"name": "name"
},
{
"name": "precision"
},
{
"name": "statistic_group_id"
},
{
"name": "team_id"
},
{
"name": "type",
"value": "statistic"
}
]
}
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]

View File

@@ -0,0 +1,27 @@
{% 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,116 +0,0 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ event.data.formatted_title }}{% endblock %}
{% block page_heading %}{{ event.data.formatted_title }}{% endblock %}
{% block page_subheading %}{{ event.data.start_date }}, {{ event.data.location_name }}{% endblock %}
{% block content %}
<h6>Lineup</h6>
<form method="post" action="{% url 'teamsnap_submit_lineup' team_id=team_id event_id=event_id %}">
{{ formset.management_form }}
{% csrf_token %}
<div class="table-responsive">
<table class="table table-striped table-sm" id="table-event-lineup">
<tbody id="tablebody-event-lineup">
{% for form in formset %}
{% if form.event_lineup_entry_id.value %}
<tr>
{{ form.event_lineup_entry_id.as_hidden }}
{{ form.event_lineup_id.as_hidden }}
{{ form.event_id.as_hidden }}
{{ form.member_id.as_hidden }}
{{ form.sequence.as_hidden }}
{{ form.label.as_hidden }}
{{ form.member_name.as_hidden }}
<td class="col-1 px-1">
{{ forloop.counter }}
</td>
<td class="col-auto">
{{ form.member_name.value }}
</td>
<td class="col-3">
<div class="form-select-sm">{{ form.label }}</div>
</td>
<td class="col-1">
<div class="d-flex">
<div class="px-1">
<i class="bi bi-dash-circle text-danger"></i>
{{ form.DELETE.as_hidden }}
</div>
</div>
</td>
<td class="col-1 drag-handle">
<i class="bi bi-grip-vertical text-secondary"></i>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
<input class="btn btn-success" type="submit" value="Submit">
</form>
<h6>Bench</h6>
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# <th scope="row">#}
{# <td>Status</td>#}
{# <td>Last</td>#}
{# <td>First</td>#}
{# </th>#}
{# </thead>#}
{% for availability in availablities %}
{% if availability.data.member_is_non_player is False %}
<tr>
<td class="col-1">
{% if availability.data.status_code == 1 %}
<i class="bi bi-check-circle-fill text-success"></i>
{% endif %}
{% if availability.data.status_code == 0 %}
<i class="bi bi-x-circle-fill text-danger"></i>
{% endif %}
{% if availability.data.status_code == None %}
<i class="bi bi-circle"></i>
{% endif %}
{% if availability.data.status_code == 2 %}
<i class="bi bi-circle-half text-info"></i>
{% endif %}
</td>
<td class="col-auto">
{{ availability.data.member_first_name }} {{ availability.data.member_last_name }}
</td>
<td class="col-3">
</td>
<td class="col-1">
<div class="d-flex">
<div class="px-1">
<i class="bi bi-plus-circle"></i>
</div>
</div>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
<script src="{% static 'js/Sortable.js' %}"></script>
<script id="sortable">
var lineup = new Sortable.create(
document.getElementById('tablebody-event-lineup'), {
animation: 150,
ghostClass:"ghost",
handle: ".drag-handle",
group:{
put:true,
pull:true
},
});
</script>
{% endblock %}

View File

@@ -0,0 +1,76 @@
<html lang="en">
{% extends "base.html" %}{% load static %}
{% block title %} {{ event.data.formatted_title }} - Instagenerator{% endblock %}
{% block page_heading %}
<div class="row d-inline-flex">
<div class="col">
<img src="{% static 'teamsnap/ig/graphics/hounds-insta.jpg' %}" class="mx-auto d-block img-fluid shadow-sm" style="height:30px;border-radius: 8px;">
</div>
<div class="col text-nowrap px-0">Hounds Instagenerator</div>
</div>
{% endblock %}
{% block page_subheading %}{{ event.data.formatted_title }}, {{ event.data.start_date }}, {{ event.data.location_name }}{% endblock %}
{% block content %}
<form method="get" action="generate">
<div class="mb-3">
<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>
</div>
<div class="mb-3">
<legend class="">Background</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="background" id="backgroundLocation" checked value="location">
<label class="form-check-label" for="backgroundLocation">
Location
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="background" id="backgroundTransparent" value="transparent">
<label class="form-check-label" for="backgroundTransparent">
Transparent
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="background" id="badge_only" value="badge">
<label class="form-check-label" for="badge_only">
Badge only
</label>
</div>
</div>
<div class="mb-3">
<legend class="">Layout</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="dimensions" id="square" checked value="1080x1080">
<label class="form-check-label" for="square">
Square
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="dimensions" id="portrait" value="1080x1920">
<label class="form-check-label" for="portrait">
Portrait
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="dimensions" id="portrait" value="1920x1080">
<label class="form-check-label" for="portrait">
Landscape
</label>
</div>
</div>
<div class="d-lg-flex justify-content-lg-end align-items-lg-center mb-3">
<button type="submit" class="btn btn-primary btn-block">Get Image</button>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ event.data.formatted_title }}{% endblock %}
{% block page_heading %}{{ event.data.formatted_title }}{% endblock %}
{% block page_subheading %}{{ event.data.start_date }}, {{ event.data.location_name }}{% endblock %}
{% block content %}
{% 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 %}

View File

@@ -0,0 +1,95 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ title }}{% endblock %}
{% csrf_token %}
{% block page_heading %}
<div class="row d-inline-flex">
<div class="col">
Schedule
</div>
</div>
{% endblock %}
{% block content %}
<form method="get" action="{% url 'teamsnap_edit_multiple_lineups' team_id=team_id%}">
{{ formset.management_form }}
<input class="btn btn-sm btn-outline-primary text-nowrap" type="submit" value="Submit">
{% load tz %}
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# </thead>#}
<tbody>
{% for form in formset %}
<tr>
{{ form.event_id }}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
<!-- create_normal.html :: part 4 -->
<script type='text/javascript'>
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\\d+)');
var replacement = prefix + '-' + ndx;
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function cloneMore(selector, prefix) {
var newElement = $(selector).clone(true);
var total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input:not([type=button]):not([type=submit]):not([type=reset])').each(function() {
var name = $(this).attr('name').replace('-' + (total-1) + '-', '-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
});
newElement.find('label').each(function() {
var forValue = $(this).attr('for');
if (forValue) {
forValue = forValue.replace('-' + (total-1) + '-', '-' + total + '-');
$(this).attr({'for': forValue});
}
});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
var conditionRow = $('.form-row:not(:last)');
conditionRow.find('.btn.add-form-row')
.removeClass('btn-success').addClass('btn-danger')
.removeClass('add-form-row').addClass('remove-form-row')
.html('<span class="glyphicon glyphicon-minus" aria-hidden="true"></span>');
return false;
}
function deleteForm(prefix, btn) {
var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total > 1){
btn.closest('.form-row').remove();
var forms = $('.form-row');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
return false;
}
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row:last', 'form');
return false;
});
$(document).on('click', '.remove-form-row', function(e){
e.preventDefault();
deleteForm('form', $(this));
return false;
});
</script>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ title }}{% endblock %}
{% csrf_token %}
{% block page_heading %}
<div class="row d-inline-flex">
<div class="col">
Schedule
</div>
</div>
{% endblock %}
{% block content %}
<form method="get" action="{% url 'teamsnap_edit_multiple_lineups' team_id=team_id%}">
{{ formset.management_form }}
<input class="btn btn-sm btn-outline-primary text-nowrap" type="submit" value="Submit">
{% load tz %}
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# </thead>#}
<tbody>
{% for form in formset %}
{{ form.event_id.as_hidden }}
<tr>
<td>
{{ 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>
</td>
<td>
{{ form.event.data.start_date | localtime}}
</td>
<td>
{{ form.event.data.location_name }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,100 @@
{% 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>
{% for event_data in contexts %}
<td>
<h4>{{ event_data.event.data.formatted_title }}</h4>
<h6 class="text-muted" >{{ event_data.data.start_date }}</h6>
{% with 'P C 1B 2B 3B SS LF CF RF EH DH' as position_list %}
{% for position in position_list.split %}
<span class="position-status fw-bold small px-1" id="position-status-{{ position }}">{{ position }}</span>
{% endfor %}
{% endwith %}
</td>
{% endfor %}
</tr>
<tr>
<td class="bg-light" colspan="{{ contexts|length }}"><h4>Lineup</h4></td>
</tr>
<tr>
{% for event_data in contexts %}
<td class="align-top">
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=event_data.formset_lineup table_id="players-lineup" %}
</td>
{% endfor %}
</tr>
<tr>
<td class="bg-light" colspan="{{ contexts|length }}"><h4>Bench</h4></td>
</tr>
<tr>
{% for event_data in contexts %}
<td class="align-top">
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=event_data.formset_bench table_id="players-bench" %}
</td>
{% endfor %}
</tr>
<tr>
<td class="bg-light" colspan="{{ contexts|length }}"><h4>Out</h4></td>
</tr>
<tr>
{% for event_data in contexts %}
<td class="align-top">
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=event_data.formset_out table_id="players-bench" %}
</td>
{% endfor %}
</tr>
</tbody>
</table>
{# <div class="align-content-start">#}
{# <div class = "bg-light rounded-3 p-2">#}
{# <div class="row">#}
{# {% with 'P C 1B 2B 3B SS LF CF RF EH DH' as position_list %}#}
{# {% for position in position_list.split %}#}
{# <span class="position-status fw-bold small px-1" id="position-status-{{ position }}">{{ position }}</span>#}
{# {% endfor %}#}
{# {% endwith %}#}
{# </div>#}
{# </div>#}
{# </div>#}
{# <form method="post" action="{% url 'teamsnap_submit_lineup' team_id=team_id event_id=event_id%}">#}
{# {{ formset.management_form }}#}
{# {% csrf_token %}#}
{# <h4 class="border-bottom">Lineup</h4>#}
{# {% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_lineup table_id="players-lineup" %}#}
{# <input class="btn btn-success" type="submit" value="Submit">#}
{##}
{# <h4 class="border-bottom">Bench</h4>#}
{# {% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_bench table_id="players-bench" %}#}
{##}
{# <h4 class="border-bottom">Out</h4>#}
{# {% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_out table_id="players-out" %}#}
{# </form>#}
<script src="{% static 'js/Sortable.js' %}"></script>
<script src="{% static 'teamsnap/js/lineup-table.js' %}"></script>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,28 @@
<div>
<div class="align-content-start">
<div class = "bg-light rounded-3 p-2">
{# <div class="row">#}
{% with 'P C 1B 2B 3B SS LF CF RF EH DH' as position_list %}
{% for position in position_list.split %}
<span class="position-status fw-bold small px-1" id="position-status-{{ position }}">{{ position }}</span>
{% endfor %}
{% endwith %}
{# </div>#}
</div>
</div>
<form method="post" action="{% url 'teamsnap_submit_lineup' team_id=team_id event_id=event_id%}">
{{ formset.management_form }}
{% csrf_token %}
<h4 class="border-bottom">Lineup</h4>
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_lineup table_id="players-lineup" %}
<input class="btn btn-success" type="submit" value="Submit">
<h4 class="border-bottom">Bench</h4>
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_bench table_id="players-bench" %}
<h4 class="border-bottom">Out</h4>
{% include 'teamsnap/lineup/widgets/lineup_table.html' with formset=formset_out table_id="players-out" %}
</form>
</div>

View File

@@ -0,0 +1,59 @@
<table class="table table-sm my-0" style="min-height: 1rem" id="table-{{ table_id }}">
<tbody id="tbody-{{ table_id }}">
{% for form in formset %}
<tr data-player-id="{{ form.member.data.id }}"
data-position="{{ form.label.value }}"
data-order="{{ form.sequence.value }}">
{{ form.event_lineup_entry_id.as_hidden }}
{{ form.event_lineup_id.as_hidden }}
{{ form.event_id.as_hidden }}
{{ form.member_id.as_hidden }}
{{ form.sequence.as_hidden }}
{{ form.label.as_hidden }}
{{ form.member_name.as_hidden }}
<th class="col-1" id="sequence-member-{{ form.member.data.id }}">
{{ form.sequence.value | add:"1" }}
</th>
<td class="col-1">
<div class="mx-1">
<span id="player-order-{{ form.member.id }}" class="lineup-sequence-value">
{% if form.order.value > 0 %} {{ form.order.value | add:"1" }} {% endif %}
</span>
<span id="player-availability-{{ form.member.id }}" class="member-availability-status small">
{% if form.availability.data.status_code == 2 %}
<i class="bi bi-question-circle-fill text-info"></i>
{% elif form.availability.data.status_code == 1 %}
<i class="bi bi-check-circle-fill text-success"></i>
{% elif form.availability.data.status_code == 0 %}
<i class="bi bi-x-circle-fill text-danger"></i>
{% else %}
<i class="bi bi-question-circle"></i>
{% endif %}
</span>
</div>
</td>
<th class="">
<div class="text-nowrap">
<span class="d-none d-md-inline-block">
{{ form.member.data.first_name }}
</span>
{{ form.member.data.last_name }}
<span class="small text-muted fw-light d-none d-lg-inline-block">
#{{ form.member.data.jersey_number }}
</span>
</div>
{# <br><code><small>{{ form.statline }}</small></code>#}
</th>
<td class="col-2">
{{ form.label }}
</td>
<td class="col-1 drag-handle">
<i class="bi bi-grip-vertical text-secondary"></i>
</td>
{# <td>{{ form.instance.position }}</td>#}
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -0,0 +1,22 @@
{% 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

@@ -0,0 +1,22 @@
{% 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

@@ -0,0 +1,47 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ title }}{% endblock %}
{% block page_heading %}
<div class="row d-inline-flex">
<div class="col">
Schedule
</div>
<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="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# </thead>#}
<tbody>
{% for event in events %}
<tr>
<td>
<a href="{% url 'teamsnap_view_event' event_id=event.data.id team_id=request.user.profile.teamsnapsettings.managed_team.id%}">{{ 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

@@ -0,0 +1,137 @@
{% extends "base.html" %}{% load static %}
{% block title %} {{ event.data.formatted_title }}{% endblock %}
{% block page_heading %}{{ event.data.formatted_title }}{% endblock %}
{% block page_subheading %}{{ event.data.start_date }}, {{ event.data.location_name }}{% endblock %}
{% block content %}
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# </thead>#}
<tbody>
<tr>
<th scope="col">
Date
</th>
<td>
{{ event.data.start_date }}
</td>
</tr>
<tr>
<th scope="col">
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>
</td>
</tr>
<tr>
<th scope="col">
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>
</td>
</tr>
</tbody>
</table>
</div>
<h3>Availabilities</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# <th scope="row">#}
{# <td>Status</td>#}
{# <td>Last</td>#}
{# <td>First</td>#}
{# </th>#}
{# </thead>#}
<tbody>
<tr>
<th scope="col" class="col-1">
<i class="bi bi-check-circle-fill text-success"></i>
</th>
<td>
{{ availability_summary.data.player_going_count }}
</td>
</tr>
<tr>
<th scope="col" class="col-1">
<i class="bi bi-question-circle-fill text-info"></i>
</th>
<td>
{{ availability_summary.data.player_maybe_count }}
</td>
</tr>
<tr>
<th scope="col" class="col-1">
<i class="bi bi-x-circle-fill text-danger"></i>
</th>
<td>
{{ availability_summary.data.player_not_going_count }}
</td>
</tr>
<tr>
<th scope="col" class="col-1">
<i class="bi bi-question-circle"></i>
</th>
<td>
{{ availability_summary.data.player_unknown_count }}
</td>
</tr>
</tbody>
</table>
</div>
<div class="">
<h3>Lineup Entries</h3>
<a class="btn btn-primary btn-sm" href="{% url 'teamsnap_edit_lineup' event_id=event.data.id team_id=request.user.profile.teamsnapsettings.managed_team.id%}" role="button">Edit</a>
</div>
<div class="table-responsive">
<table class="table table-striped table-sm">
{# <thead>#}
{# <th scope="row">#}
{# <td>Status</td>#}
{# <td>Last</td>#}
{# <td>First</td>#}
{# </th>#}
{# </thead>#}
<tbody>
{% for lineup_entry in lineup_entries %}
<tr>
<td class="col-1">
{{ lineup_entry.data.sequence }}
</td>
<td>
{{ lineup_entry.data.member_name }}
</td>
<td>
{{ lineup_entry.data.label }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<h3>Functions</h3>
<div class="">
<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-block"><i class="bi bi-instagram"></i></button>
</form>
<a class="btn btn-primary" href="{% url "teamsnap_image_generator" team_id=request.user.profile.teamsnapsettings.managed_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></a>
</div>
</div>
</div>
{% endblock %}

View File

@@ -6,8 +6,19 @@ from functools import partial
from . import views
urlpatterns = [
path('', views.home, name='teamsnap home'),
path('', views.home, name='teamsnap_home'),
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('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_id>/edit_lineup/', views.edit_lineup, name='teamsnap_edit_lineup'),
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>/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/edit_lineups', views.edit_multiple_lineups, name='teamsnap_edit_multiple_lineups'),
path('<int:team_id>/multievent/choose', views.multi_lineup_choose, name='teamsnap_choose_multiple_lineups')
]

331
teamsnap/utils/gen_image.py Normal file
View File

@@ -0,0 +1,331 @@
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/ig/fonts/ScalaSans-BoldLF.otf"
font_condensed_path = "benchcoachproject/static/teamsnap/ig/fonts/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

@@ -3,7 +3,8 @@ from typing import List, Tuple
import benchcoach.models
from benchcoach.models import BenchcoachModel, Availability, Player, Team, Positioning, Event, Venue
from teamsnap.teamsnap.api import TeamSnap
from pyteamsnap.api import TeamSnap
import pyteamsnap
import teamsnap.models
from django.db.models import QuerySet
@@ -50,14 +51,14 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
}
teamsnapmodel_to_apiobject = {
teamsnap.models.Availability: teamsnap.teamsnap.api.Availability,
teamsnap.models.Event: teamsnap.teamsnap.api.Event,
# teamsnap.models.LineupEntry:teamsnap.teamsnap.api.LineupEntry, # Not implemented Yet
teamsnap.models.Location: teamsnap.teamsnap.api.Location,
teamsnap.models.Member: teamsnap.teamsnap.api.Member,
teamsnap.models.Opponent: teamsnap.teamsnap.api.Opponent,
teamsnap.models.Team: teamsnap.teamsnap.api.Team,
# teamsnap.models.User:teamsnap.teamsnap.api.User # Not implemented yet
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()}
@@ -104,14 +105,14 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
}
teamsnapmodel_to_apiobject = {
teamsnap.models.Availability: teamsnap.teamsnap.api.Availability,
teamsnap.models.Event: teamsnap.teamsnap.api.Event,
# teamsnap.models.LineupEntry:teamsnap.teamsnap.api.LineupEntry, # Not implemented Yet
teamsnap.models.Location: teamsnap.teamsnap.api.Location,
teamsnap.models.Member: teamsnap.teamsnap.api.Member,
teamsnap.models.Opponent: teamsnap.teamsnap.api.Opponent,
teamsnap.models.Team: teamsnap.teamsnap.api.Team,
# teamsnap.models.User:teamsnap.teamsnap.api.User # Not implemented yet
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):
@@ -128,9 +129,9 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
r = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance)
return r
def _update_from_teamsnapdata(self, teamsnap_instance:teamsnap.models.TeamsnapBaseModel, teamsnap_data: teamsnap.teamsnap.api.ApiObject) -> teamsnap.models.TeamsnapBaseModel:
def _update_from_teamsnapdata(self, teamsnap_instance:teamsnap.models.TeamsnapBaseModel, teamsnap_data: pyteamsnap.api.ApiObject) -> teamsnap.models.TeamsnapBaseModel:
''''''
if isinstance(teamsnap_data, teamsnap.teamsnap.api.ApiObject):
if isinstance(teamsnap_data, pyteamsnap.api.ApiObject):
teamsnap_data = teamsnap_data.data
else:
raise TypeError
@@ -390,7 +391,7 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
r['team'] = []
# Search API for objects belonging to currently managed team, and iterate
for teamsnap_data in teamsnap.teamsnap.api.Team.search(client=self.client, id=self.managed_teamsnap_team_id):
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()
@@ -417,7 +418,7 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
# 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 teamsnap.teamsnap.api.Opponent.search(**kwargs):
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
@@ -435,7 +436,7 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
# 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 teamsnap.teamsnap.api.Location.search(**kwargs):
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
@@ -455,7 +456,7 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
# 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 teamsnap.teamsnap.api.Member.search(**kwargs,
for teamsnap_data in pyteamsnap.api.Member.search(**kwargs,
is_non_player = False
):
if teamsnap_data.data['is_non_player'] == True:
@@ -478,7 +479,7 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
# 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 teamsnap.teamsnap.api.Event.search(**kwargs):
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
@@ -504,7 +505,7 @@ class TeamsnapSyncEngine(AbstractSyncEngine):
# 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 teamsnap.teamsnap.api.Availability.search(**kwargs,
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']):

View File

@@ -1,11 +1,16 @@
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
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
def edit_event(request, id):
'''
@@ -112,6 +117,7 @@ def sync_from_teamsnap(request, object_name=None, object_id=None):
return HttpResponse(404)
def import_teamsnap(request):
TEAM_ID = request.user.profile.teamsnapsettings.managed_team.id
TOKEN = request.user.profile.teamsnap_access_token
@@ -127,5 +133,437 @@ def import_teamsnap(request):
return redirect('teamsnap home')
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)
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:
pass
return render(request, "teamsnap/schedule.html", context={"events":ts_events.values(), "team_id":team_id})
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)
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_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}
members = []
return render(request, "teamsnap/view_event.html", context={
"availability_summary":ts_availability_summary,
"event":ts_event,
"availablities":[],
"lineup_entries": ts_lineup_entries,
})
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
def opponent(request, 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
def edit_lineup(request, event_id, team_id):
TOKEN = request.user.profile.teamsnap_access_token
from pyteamsnap.api import TeamSnap, Event, Availability, Member, EventLineupEntry, EventLineup, AvailabilitySummary, Opponent
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)
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]
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}
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', {})
}
)
members = sorted(members, key=lambda d: (
{
None:3, # No Response
0:2, # No
2:1, # Maybe
1:0 # Yes
}.get(d['availability'].get('status_code')),
d['member'].get('last_name'))
)
from teamsnap.forms import LineupEntryFormset, LineupEntryForm
formset = LineupEntryFormset(
initial=[
{
"event_lineup_entry_id" : member['lineup_entry'].get('id'),
"event_lineup_id" : member['lineup_entry'].get('event_lineup_id'),
"event_id": event_id,
"member_id" : member['member']['id'],
"sequence" : member['lineup_entry'].get('sequence'),
"label" : member['lineup_entry'].get('label'),
}
for member in members
]
)
for form in formset:
form.member = ts_member_lookup.get(form['member_id'].initial)
form.availability = ts_availability_lookup.get(form['member_id'].initial)
formset_lineup = [form for form in formset if form.initial.get('event_lineup_entry_id')]
formset_lineup = sorted(
formset_lineup,
key=lambda d: d.initial.get('sequence',100)
)
formset_bench = [form for form in formset if
form not in formset_lineup and
form.availability.data['status_code'] in [2, 1]
]
formset_out = [form for form in formset if
form not in formset_lineup and
form not in formset_bench and
not form.member.data['is_non_player']
]
return render(request, "teamsnap/lineup/edit.html", context={
"team_id": team_id,
"event_id": event_id,
"event": ts_event,
"formset": formset,
"formset_lineup":formset_lineup,
"formset_bench": formset_bench,
"formset_out": formset_out,
"lineup": ts_lineup
})
def edit_multiple_lineups(request, 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)
ts_events = Event.search(client, team_id=team_id)
EventChooseFormset = formset_factory(EventChooseForm)
formset = EventChooseFormset(request.GET)
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(",")
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_lineup_entries = [i for i in ts_bulkload if isinstance(i, EventLineupEntry) and i.data['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}
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', {})
}
)
members = sorted(members, key=lambda d: (
{
None:3, # No Response
0:2, # No
2:1, # Maybe
1:0 # Yes
}.get(d['availability'].get('status_code')),
d['member'].get('last_name'))
)
from teamsnap.forms import LineupEntryFormset, LineupEntryForm
formset = LineupEntryFormset(
initial=[
{
"event_lineup_entry_id" : member['lineup_entry'].get('id'),
"event_lineup_id" : member['lineup_entry'].get('event_lineup_id'),
"event_id": event_id,
"member_id" : member['member']['id'],
"sequence" : member['lineup_entry'].get('sequence'),
"label" : member['lineup_entry'].get('label'),
}
for member in members
]
)
for form in formset:
form.member = ts_member_lookup.get(form['member_id'].initial)
form.availability = ts_availability_lookup.get(form['member_id'].initial)
formset_lineup = [form for form in formset if form.initial.get('event_lineup_entry_id')]
formset_lineup = sorted(
formset_lineup,
key=lambda d: d.initial.get('sequence',100)
)
formset_bench = [form for form in formset if
form not in formset_lineup and
form.availability.data['status_code'] in [2, 1]
]
formset_out = [form for form in formset if
form not in formset_lineup and
form not in formset_bench and
not form.member.data['is_non_player']
]
contexts.append({
"event":ts_event,
"formset": formset,
"formset_bench":formset_bench,
"formset_lineup":formset_lineup,
"formset_out":formset_out
})
return render(request, "teamsnap/lineup/multiple_edit.html", context={
"team_id": team_id,
"contexts":contexts
})
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)
ts_lineup = EventLineup.search(client, event_id=event_id)
event_lineup_id = ts_lineup[0].data['id']
if request.GET:
return HttpResponseNotAllowed()
if request.POST:
formset = LineupEntryFormset(request.POST)
if formset.is_valid():
r = []
for form in formset:
data = form.cleaned_data
if data.get('event_lineup_entry_id'):
event_lineup_entry = EventLineupEntry.get(client, id=data.get('event_lineup_entry_id'))
event_lineup_entry.data.update(data)
# breakpoint()
r.append(event_lineup_entry.put())
# breakpoint()
pass
elif data.get('sequence') is not None and data.get('label'):
event_lineup_entry = EventLineupEntry.new(client)
event_lineup_entry.data.update(data)
event_lineup_entry.data.update({"event_lineup_id":event_lineup_id})
r.append(event_lineup_entry.post())
else:
pass
else:
# breakpoint()
pass
# breakpoint()
pass
return HttpResponse(f'{team_id} {event_id}')
pass
return HttpResponse(f'{team_id} {event_id}')
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})
# @app.route('/get_matchup_image')
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")
def multi_lineup_choose(request, team_id):
from teamsnap.forms import EventChooseForm
from django.forms import formset_factory
if 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})

View File

@@ -3,11 +3,9 @@
<head>
<title>{% block title %}Title{% endblock %}</title>
{% include 'base_layout.html' %}
{% include 'head.html' %}
</head>
<body class="bg-light">
<header class="min-vw-100">
<div>
@@ -18,24 +16,12 @@
</header>
<div class="d-inline-flex min-vw-100 min-vh-100" style="padding-top:3rem;">
<div id="sidebar" class="">
{% block sidebar %}
{% include 'sidebar.html' %}
{% endblock %}
</div>
<div class="w-100 m-0">
<div class="bg-white border border-bottom">
<h1 class="m-2">{% block page_heading %}{% endblock %}</h1>
<h6 class="m-2 text-muted">{% block page_subheading %}{% endblock %}</h6>
<main class="container-sm bg-white rounded-3 shadow-sm my-2">
<div class="p-2">
<h2 class="m border-bottom">{% block page_heading %}{% endblock %}</h2>
<h6 class="m text-muted">{% block page_subheading %}{% endblock %}</h6>
{% block header %}
{% endblock %}
</div>
<div class="container-fluid">
{% block content %}{% endblock %}
</div>
</div>
</div>
</body>
</html>
</main>

View File

@@ -1,9 +1,16 @@
{% extends "base.html" %}{% load static %}
{% block title %}Bench Coach Home{% endblock %}
{% block page_heading %}
<div class="text-center my-2">
<h1><img class="mx-auto" src="{% static 'benchcoach.svg' %}" style="width: 64px;" />
<strong>Welcome to Bench Coach</strong>
</h1>
</div>
{% endblock %}
{% block content %}
<div class="text-center my-2">
<h1><img class="mx-auto" src="{% static 'benchcoach.svg' %}" style="width: 64px;" /><strong>Welcome to Bench Coach</strong></h1>
<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">#}

View File

@@ -1,22 +1,48 @@
{% load static %}
<nav class="navbar navbar-dark navbar-expand-md py-0 fixed-top" style="background: #323669;height:3rem">
<a class="navbar-brand d-flex text-uppercase fw-bold m-1 mx-2 text-dark" data-bs-toggle="collapse" data-bs-target="#sidebar">
<nav class="navbar navbar-dark navbar-expand-lg" style="background: #323669">
<a class="navbar-brand text-uppercase fw-bold mx-2 text-dark">
<img class="d-inline-block" width="32" height="32" src="{% static 'benchcoach.svg' %}" />
<span class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block mx-1 my-auto text-white">bench coach</span>
<span class="text-white">bench coach</span>
</a>
<button class="navbar-toggler m-2 mx-4 p-0 d-none" data-bs-toggle="collapse" data-bs-target="#navcol-2">
<span class="visually-hidden">Toggle navigation</span>
<span class="navbar-toggler-icon m-1"></span></button>
<div id="navcol-2" class="collapse navbar-collapse">
<button class="navbar-toggler mx-2" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mx-2">
<li class="nav-item"><a class="nav-link {{ events_tab }}" href="{% url 'event list' %}">Events</a></li>
<li class="nav-item"><a class="nav-link {{ members_tab }}" href="{% url 'player list' %}">Members</a></li>
<li class="nav-item"><a class="nav-link {{ opponents_tab }}" href="{% url 'team list' %}">Opponents</a></li>
<li class="nav-item"><a class="nav-link {{ venues_tab }}" href="{% url 'venue list' %}">Venues</a></li>
<li class="nav-item ">
<a class="nav-link{% if request.resolver_match.url_name == 'home' %} active{% endif %} text-decoration-none" href="{% url 'home'%}">
<i class="bi bi-house-fill"></i>
<span class="">
Home
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if request.resolver_match.url_name == 'teamsnap_home' %} active{% endif %} text-decoration-none" href="{% url 'teamsnap_home'%}">
<i class="bi bi-asterisk"></i>
<span class="">
Teamsnap
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if request.resolver_match.url_name == 'teamsnap_schedule' %} active{% endif %} text-decoration-none" href="{% url 'teamsnap_schedule' team_id=request.user.profile.teamsnapsettings.managed_team.id%}">
<i class="bi bi-calendar-fill"></i>
<span class="">
Schedule
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if request.resolver_match.url_name == 'teamsnap_choose_multiple_lineups' %} active{% endif %} text-decoration-none" href="{% url 'teamsnap_choose_multiple_lineups' team_id=request.user.profile.teamsnapsettings.managed_team.id%}?num=3">
<i class="bi bi-layout-three-columns"></i>
<span class="">
Multi-Lineup
</span>
</a>
</li>
</ul>
<span class="mx-auto"></span>
<span class="navbar-text"></span>
</div>
</div>
</nav>

View File

@@ -1,53 +1,14 @@
<div class="bg-dark h-100">
<ul class="nav nav-pills flex-column mb-auto">
<li class="nav-item">
<a class="nav-link {{ events_tab }} text-white" href="{% url 'event list' %}">
<a class="nav-link {{ events_tab }} text-white" href="{% url 'teamsnap_schedule' team_id=request.user.profile.teamsnapsettings.managed_team.id%}">
<div class="d-flex flex-nowrap">
<i class="bi bi-calendar3"></i>
<span class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block mx-1 my-auto text-white">
Events
Schedule
</span>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ members_tab }} text-white" href="{% url 'player list' %}">
<div class="d-flex flex-nowrap">
<i class="bi bi-person-fill"></i>
<span class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block mx-1 my-auto text-white">
Players
</span>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ opponents_tab }} text-white" href="{% url 'team list' %}">
<div class="d-flex flex-nowrap">
<i class="bi bi-people-fill"></i>
<span class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block mx-1 my-auto text-white">
Teams
</span>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ venues_tab }} text-white" href="{% url 'venue list' %}">
<div class="d-flex flex-nowrap"><i class="bi bi-geo"></i>
<span class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block mx-1 my-auto text-white">
Venues
</span></div>
</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" href="{% url 'teamsnap home' %}">
<div class="d-flex flex-nowrap"><i class="bi bi-asterisk"></i>
<span class="d-none d-print-inline-block d-sm-inline-block d-md-inline-block d-lg-inline-block d-xl-inline-block d-xxl-inline-block mx-1 my-auto text-white">
Sync
</span></div>
</a>
</li>
</ul>
</div>