diff --git a/benchcoach/utils/teamsnap_sync_engine.py b/benchcoach/utils/teamsnap_sync_engine.py deleted file mode 100644 index 4d2e7a6..0000000 --- a/benchcoach/utils/teamsnap_sync_engine.py +++ /dev/null @@ -1,354 +0,0 @@ -import django.db.models -from typing import List, Tuple -from benchcoach.models import Availability, Player, Team, Positioning, Event, Venue -from teamsnap.teamsnap.api import TeamSnap -import teamsnap.models - -from benchcoach.utils.sync_engine import AbstractSyncEngine - -class TeamsnapSyncEngine(AbstractSyncEngine): - models = [ - Availability, - Player, - Team, - # Positioning, # Not Implemented - Event, - Venue - ] - - def __init__(self, managed_team_teamsnap_id, teamsnap_token): - self.managed_teamsnap_team_id = managed_team_teamsnap_id - self.client = TeamSnap(token=teamsnap_token) - - def _update_teamsnapdb_from_teamsnapapi(self, teamsnap_instance): - ApiObject = { - teamsnap.models.Availability:teamsnap.teamsnap.api.Availability, - teamsnap.models.Member:teamsnap.teamsnap.api.Member, - teamsnap.models.Team:teamsnap.teamsnap.api.Team, - teamsnap.models.Opponent:teamsnap.teamsnap.api.Opponent, - # teamsnap.models.LineupEntry # Not Implemented - teamsnap.models.Event:teamsnap.teamsnap.api.Event, - teamsnap.models.Location:teamsnap.teamsnap.api.Location - }.get(teamsnap_instance._meta.model) - teamsnap_model = teamsnap_instance._meta.model - new_data = ApiObject.get(client=self.client, id=teamsnap_instance.id).data - obj, created = self._update_or_create_from_teamsnapapi(teamsnap_model, new_data) - return [(obj, created)] - - def _update_or_create_from_teamsnapapi(self, teamsnap_model, teamsnap_data, create_benchcoach_object = False): - related_objects = {} - fields = ['id', 'created_at', 'updated_at'] - if teamsnap_model == teamsnap.models.Event: - fields += [ - 'label', - 'start_date', - 'formatted_title', - 'points_for_opponent', - 'points_for_team', - 'is_game', - 'game_type' - ] - if teamsnap_data.get('location_id'): - related_objects['location'] = self._update_or_create_from_teamsnapapi( - teamsnap.models.Location, - {'id':teamsnap_data['location_id']} - ) - if teamsnap_data.get('opponent_id'): - related_objects['opponent'] = self._update_or_create_from_teamsnapapi( - teamsnap.models.Opponent, - {'id':teamsnap_data['opponent_id']} - ) - - elif teamsnap_model == teamsnap.models.Opponent: - fields += ['name'] - - elif teamsnap_model == teamsnap.models.Team: - fields += ['name'] - - elif teamsnap_model == teamsnap.models.Location: - fields += ['name'] - - elif teamsnap_model == teamsnap.models.Member: - fields += [ - 'first_name', - 'last_name', - 'jersey_number', - 'is_non_player' - ] - elif teamsnap_model == teamsnap.models.Availability: - fields += ['status_code'] - - related_objects['member'] = self._update_or_create_from_teamsnapapi( - teamsnap.models.Member, - {'id': teamsnap_data['member_id']} - ) - - related_objects['event'] = self._update_or_create_from_teamsnapapi( - teamsnap.models.Event, - {'id': teamsnap_data['event_id']} - ) - - else: - raise ValueError - - if teamsnap_data.get('team_id'): - related_objects['team'] = self._update_or_create_from_teamsnapapi(teamsnap.models.Team, - {"id":teamsnap_data['team_id']}) - - data = {field: teamsnap_data[field] for field in fields if teamsnap_data.get(field) != None} - id = data.pop('id') - instance, created = teamsnap_model.objects.update_or_create(id=id, defaults=data) - r_related_objects = [] - for related_object_name, related_objectcreated_list in related_objects.items(): - related_object, created = related_objectcreated_list[0] #FIXME This can't be right, do we need a list for related? - setattr(instance, related_object_name, related_object) - r_related_objects.append((related_object, created)) - instance.save() - # if create_benchcoach_object: - # ben - return [(instance, created)] + r_related_objects - - def _update_teamsnapdb_to_benchcoachdb(self, benchcoach_instance, teamsnap_instance, - create_if_doesnt_exist: bool = False) -> List[Tuple[django.db.models.Model, bool]]: - ''' Function to update from a teamsnap object to Benchcoach object. - - :param d: The information to update. - :param teamsnap_object: The teamsnap object from which to update. - :param create_benchcoach_object: If true, will create the benchcoach object if it doesn't exist - :param create_related: This is here for decoration only. It doesn't do anything. - :return: a list of tuples in the form (obj, did_create) for created or modified objects. - ''' - - if isinstance(teamsnap_instance, teamsnap.models.Event): - benchcoach_model = Event - - d = { - 'start': teamsnap_instance.start_date, - } - - if teamsnap_instance.team: - if teamsnap_instance.team.benchcoach_object: - if teamsnap_instance.game_type == "Home": - d['home_team'] = teamsnap_instance.team.benchcoach_object - elif teamsnap_instance.game_type == "Away": - d['away_team'] = teamsnap_instance.team.benchcoach_object - elif not teamsnap_instance.team.benchcoach_object: - raise Team.DoesNotExist - - if teamsnap_instance.opponent: - if teamsnap_instance.opponent.benchcoach_object: - if teamsnap_instance.game_type == 'Home': - d['away_team'] = teamsnap_instance.opponent.benchcoach_object - elif teamsnap_instance.game_type == 'Away': - d['home_team'] = teamsnap_instance.opponent.benchcoach_object - elif not teamsnap_instance.opponent.benchcoach_object: - raise Team.DoesNotExist - - if teamsnap_instance.location: - if teamsnap_instance.location.benchcoach_object: - if teamsnap_instance.location: - d['venue'] = teamsnap_instance.location.benchcoach_object - elif not teamsnap_instance.location.benchcoach_object: - raise Venue.DoesNotExist - - elif isinstance(teamsnap_instance, teamsnap.models.Opponent): - benchcoach_model = Team - d = { - 'name': teamsnap_instance.name, - } - - elif isinstance(teamsnap_instance, teamsnap.models.Team): - benchcoach_model = Team - d = { - 'name': teamsnap_instance.name, - } - - elif isinstance(teamsnap_instance, teamsnap.models.Location): - benchcoach_model = Venue - d = { - 'name': teamsnap_instance.name, - } - - elif isinstance(teamsnap_instance, teamsnap.models.Member): - benchcoach_model = Player - d = { - 'first_name': teamsnap_instance.first_name, - 'last_name': teamsnap_instance.last_name, - 'jersey_number': teamsnap_instance.jersey_number, - } - - elif isinstance(teamsnap_instance, teamsnap.models.Availability): - benchcoach_model = Availability - - translation = { - teamsnap_instance.YES: Availability.YES, - teamsnap_instance.NO: Availability.NO, - teamsnap_instance.MAYBE: Availability.MAYBE - } - - d = { - 'available': translation.get(teamsnap_instance.status_code, Availability.UNKNOWN), - 'player': teamsnap_instance.member.benchcoach_object, - 'event': teamsnap_instance.event.benchcoach_object - } - - r = [] - - if teamsnap_instance.member.benchcoach_object: - d['player'] = teamsnap_instance.member.benchcoach_object - elif not teamsnap_instance.member.benchcoach_object: - raise Availability.DoesNotExist - - if teamsnap_instance.event.benchcoach_object: - d['event'] = teamsnap_instance.event.benchcoach_object - elif not teamsnap_instance.event.benchcoach_object: - raise Event.DoesNotExist - - else: - raise ValueError - - r=[] - if teamsnap_instance.benchcoach_object: - benchcoach_object = benchcoach_model.objects.filter(id=teamsnap_instance.benchcoach_object.id) - benchcoach_object.update(**d) - created = False - r.append((benchcoach_object.first(), created)) - # elif not teamsnap_instance.benchcoach_object and create_if_doesnt_exist: - elif not teamsnap_instance.benchcoach_object: - raise django.db.models.Model.DoesNotExist - - return r - - def _find_counterpart(self, instance): - instance_type = type(instance) - if instance_type == Availability: - counterpart_instance = instance.teamsnap_availability - - elif instance_type == Player: - counterpart_instance = instance.teamsnap_member - - elif instance_type == Event: - counterpart_instance = instance.teamsnap_event - - elif instance_type == Venue: - counterpart_instance = instance.teamsnap_location - - elif instance_type == Team: - if hasattr(instance, 'teamsnap_opponent'): - counterpart_instance = instance.teamsnap_opponent - elif hasattr(instance, 'teamsnap_team'): - counterpart_instance = instance.teamsnap_team - else: - raise ValueError("instance doesn't seem to be an teamsnap opponent or a teamsnap team") - - elif instance_type == Positioning: - counterpart_instance = instance.teamsnap_lineupentry - - if not counterpart_instance: raise Exception() - - return counterpart_instance - - def _fetch_new_data(self, instance): - api_object = instance.ApiObject.get(client=self.client, id=instance.id) - return api_object.data - - def _fetch_sync(self, instance): - r=[] - counterpart_instance = self._find_counterpart(instance) - r += self._update_teamsnapdb_from_teamsnapapi(counterpart_instance) - r += self._update_teamsnapdb_to_benchcoachdb(instance, counterpart_instance) - return r - - def _sync_qs (self, qs, direction): - if qs.model not in self.models: - raise TypeError(f"Sync engine does not sync {qs.model} models") - - r=[] - - for instance in qs: - r += self._sync_instance(instance, direction=direction) - - return r - - def _sync_instance(self, instance, direction, data=None): - r=[] - if direction == 'download': - r += self._fetch_sync(instance) - - elif direction == 'upload': - raise NotImplementedError('Uploading not supported by this sync engine yet.') - else: - raise TypeError(f"Direction {direction} not supported. 'upload' or 'download' must be specified") - - return r - - - def sync(self, qs: django.db.models.QuerySet = None, instance: django.db.models.Model = None, - direction='download') -> List[Tuple[django.db.models.Model, bool]]: - if not qs and not instance: - raise TypeError(f"sync requires either a QuerySet or model instance to be provided") - if qs and instance: - raise TypeError(f"sync requires either a QuerySet or model instance to be provided, but not both") - elif qs: - r = self._sync_qs(qs, direction) - elif instance: - r = self._sync_instance(instance, direction) - - return r - - def import_items(self, object_name): - object_names = [] - if object_name == 'team': - object_names += ['opponent', 'team'] - elif object_name == 'player': - object_names = ['member'] - elif object_name == 'venue': - object_name == ['location'] - elif object_name in [model.__name__.lower() for model in self.models]: - object_names += [object_name] - - if len(object_names) == 0: - raise ValueError('no valid keyword provided') - - for object_name in object_names: - Object = { - obj.__name__.lower(): obj - for obj in - [ - teamsnap.models.Availability, - teamsnap.models.Event, - # teamsnap.models.LineupEntry, - teamsnap.models.Location, - teamsnap.models.Member, - teamsnap.models.Opponent, - teamsnap.models.Team, - # teamsnap.models.User - ] - }.get(object_name) - - ApiObject = { - apiobj.__name__.lower(): apiobj - for apiobj in - [ - teamsnap.teamsnap.api.Availability, - teamsnap.teamsnap.api.Event, - # teamsnap.teamsnap.api.LineupEntry, - teamsnap.teamsnap.api.Location, - teamsnap.teamsnap.api.Member, - teamsnap.teamsnap.api.Opponent, - teamsnap.teamsnap.api.Team, - # teamsnap.teamsnap.api.User - ] - }.get(object_name) - - pass - - if not Object: raise KeyError(f"key {object_name} not found.") - r = [] - - a = ApiObject.search(self.client, team_id=self.managed_teamsnap_team_id) - for _a in a: - response = self._update_or_create_from_teamsnapapi(Object, _a.data) - r += response - - return r \ No newline at end of file diff --git a/benchcoach/utils/test_sync.py b/benchcoach/utils/test_sync.py deleted file mode 100644 index b1ccf50..0000000 --- a/benchcoach/utils/test_sync.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.test import TestCase -import os - -from benchcoach.utils.teamsnap_sync_engine import TeamsnapSyncEngine - -import benchcoach.models - -TEAMSNAP_TOKEN = os.environ['TEAMSNAP_TOKEN'] -TEAM_TEAMSNAP_ID = os.environ['TEAM_TEAMSNAP_ID'] - -syncengine = TeamsnapSyncEngine(managed_team_teamsnap_id=TEAM_TEAMSNAP_ID, teamsnap_token=TEAMSNAP_TOKEN) -r=syncengine.import_items('event') -pass - -class TestEventModel(TestCase): - fixtures = ['minimal'] - - def setUp(self): - self.syncengine = TeamsnapSyncEngine(managed_team_teamsnap_id=TEAM_TEAMSNAP_ID, teamsnap_token=TEAMSNAP_TOKEN) - - def test_all_models(self): - for Model in self.syncengine.models: - with self.subTest(): - self.syncengine.import_items(Model.__name__.lower()) - pass \ No newline at end of file diff --git a/teamsnap/models.py b/teamsnap/models.py index 40a01c9..e00027d 100644 --- a/teamsnap/models.py +++ b/teamsnap/models.py @@ -25,8 +25,6 @@ class Team(TeamsnapBaseModel): name = models.CharField(max_length=50, null=True) benchcoach_object = models.OneToOneField( benchcoach.models.Team, - null=True, - blank=True, on_delete=models.CASCADE, related_name="teamsnap_team" ) @@ -74,8 +72,6 @@ class Opponent(TeamsnapManagedObjectModel): name = models.CharField(max_length=50, null=True) benchcoach_object = models.OneToOneField( benchcoach.models.Team, - null=True, - blank=True, on_delete=models.CASCADE, related_name="teamsnap_opponent" ) @@ -86,8 +82,6 @@ class Location(TeamsnapManagedObjectModel): name = models.CharField(max_length=50, null=True) benchcoach_object = models.OneToOneField( benchcoach.models.Venue, - null=True, - blank=True, on_delete=models.CASCADE, related_name="teamsnap_location" ) @@ -101,8 +95,6 @@ class Member(TeamsnapManagedObjectModel): name = models.CharField(max_length=50, null=True) benchcoach_object = models.OneToOneField( benchcoach.models.Player, - null=True, - blank=True, on_delete=models.CASCADE, related_name="teamsnap_member" ) @@ -126,8 +118,6 @@ class Event(TeamsnapManagedObjectModel): type = 'event' benchcoach_object = models.OneToOneField( benchcoach.models.Event, - null=True, - blank=True, on_delete=models.CASCADE, related_name="teamsnap_event" ) @@ -161,8 +151,6 @@ class Availability(TeamsnapManagedObjectModel): member = models.ForeignKey(Member, null=True, on_delete=models.CASCADE) benchcoach_object = models.OneToOneField( benchcoach.models.Availability, - null=True, - blank=True, on_delete=models.CASCADE, related_name="teamsnap_availability" ) @@ -193,8 +181,6 @@ class LineupEntry(TeamsnapManagedObjectModel): ] benchcoach_object = models.OneToOneField( benchcoach.models.Positioning, - null=True, - blank=True, on_delete=models.CASCADE, related_name="teamsnap_lineupentry" ) diff --git a/teamsnap/templates/teamsnap/home.html b/teamsnap/templates/teamsnap/home.html index 301a8a3..df837ab 100644 --- a/teamsnap/templates/teamsnap/home.html +++ b/teamsnap/templates/teamsnap/home.html @@ -9,6 +9,10 @@ {% include 'messages.html' %} + + Import + + - - {% endfor %} diff --git a/teamsnap/urls.py b/teamsnap/urls.py index 94aace3..264e8fc 100644 --- a/teamsnap/urls.py +++ b/teamsnap/urls.py @@ -15,6 +15,7 @@ urlpatterns = [ path('sync_benchcoach_db', views.sync_teamsnapdb_to_benchcoachdb, name="sync benchcoach"), path('update/', views.update_teamsnapdb_from_teamsnapapi, name="update"), path('send/', views.send_to_benchcoach, name="send"), - path('sync/', views.sync, name="sync") + path('sync/', views.sync, name="sync"), + path('import/', views.import_teamsnap, name="import") # path('import_teamsnap', views.import_teamsnap, name="import teamsnap"), ] \ No newline at end of file diff --git a/teamsnap/utils/teamsnap_sync_engine.py b/teamsnap/utils/teamsnap_sync_engine.py new file mode 100644 index 0000000..ef1e386 --- /dev/null +++ b/teamsnap/utils/teamsnap_sync_engine.py @@ -0,0 +1,457 @@ +import django.db.models +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 +import teamsnap.models +from django.db.models import QuerySet + +from benchcoach.utils.sync_engine import AbstractSyncEngine + +class TeamsnapSyncEngine(AbstractSyncEngine): + models = [ + Availability, + Player, + Team, + # Positioning, # Not Implemented + Event, + Venue + ] + + def __init__(self, managed_team_teamsnap_id, teamsnap_token): + self.managed_teamsnap_team_id = managed_team_teamsnap_id + self.client = TeamSnap(token=teamsnap_token) + + def _bulk_sync_from_teamsnap(self, qs:QuerySet): + # ------------------------------------------------------------------------------------------- + # I hate having this translation. What I really want is just a property for "teamsnap_object" + # which would simplify all this, but I couldn't figure out how to implement in the + # teamsnap model foreign key and "related_name" that didn't cause conflicts. I don't + # think I need to be too much smarter to figure this out, but alas I am not smart enough. + # ------------------------------------------------------------------------------------------- + benchcoachmodel_to_teamsnapfield = { + Availability:'teamsnap_availability', + Player:'teamsnap_member', + Team:'teamsnap_opponent', + # Positioning:'teamsnap_lineupentry', # Not Implemented Yet, but will be 'teamsnap_lineupentry' + Event:'teamsnap_event', + Venue:'teamsnap_location' + } + + teamsnapmodel_to_apiobject = { + teamsnap.models.Availability: 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 + } + + apiobject_to_teamsnapmodel = {v:k for k,v in teamsnapmodel_to_apiobject.items()} + + if isinstance(qs.first(), benchcoach.models.Team): + # This situation requires special attention because opponents and teams share a table in BenchCoach + if getattr(qs.first(), 'teamsnap_team', None): + teamsnap_attribute_name = 'teamsnap_team' + elif getattr(qs.first(), 'teamsnap_opponent', None): + teamsnap_attribute_name = 'teamsnap_opponent' + else: + teamsnap_attribute_name = benchcoachmodel_to_teamsnapfield.get(type(qs.first())) + + ids = [getattr(i, teamsnap_attribute_name).id for i in qs] + ApiObject = teamsnapmodel_to_apiobject.get(type(getattr(qs.first(), teamsnap_attribute_name))) + api_responses = ApiObject.search(client=self.client, id=",".join(ids)) + r = [] + for api_response in api_responses: + teamsnap_instance = apiobject_to_teamsnapmodel.get(type(api_response)).objects.get(id=api_response.data['id']) + response = self._update_from_teamsnapdata(teamsnap_instance, api_response) + response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, teamsnap_instance.benchcoach_object) + r.append(response) + return r + + def _sync_from_teamsnap(self, benchcoach_instance:BenchcoachModel): + + # ------------------------------------------------------------------------------------------- + # I hate having this translation. What I really want is just a property for "teamsnap_object" + # which would simplify all this, but I couldn't figure out how to implement in the + # teamsnap model foreign key and "related_name" that didn't cause conflicts. I don't + # think I need to be too much smarter to figure this out, but alas I am not smart enough. + # ------------------------------------------------------------------------------------------- + benchcoachmodel_to_teamsnapfield = { + Availability:'teamsnap_availability', + Player:'teamsnap_member', + Team:'teamsnap_opponent', + # Positioning:'teamsnap_lineupentry', # Not Implemented Yet, but will be 'teamsnap_lineupentry' + Event:'teamsnap_event', + Venue:'teamsnap_location' + } + + teamsnapmodel_to_apiobject = { + teamsnap.models.Availability: 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 + } + + if isinstance(benchcoach_instance, benchcoach.models.Team): + # This situation requires special attention because opponents and teams share a table in BenchCoach + teamsnap_instance = getattr(benchcoach_instance, 'teamsnap_team', None) + if not teamsnap_instance: teamsnap_instance = getattr(benchcoach_instance, 'teamsnap_opponent') + else: + teamsnap_instance = getattr(benchcoach_instance, benchcoachmodel_to_teamsnapfield.get(type(benchcoach_instance))) + + ApiObject = teamsnapmodel_to_apiobject.get(type(teamsnap_instance)) + api_response = ApiObject.get(self.client, teamsnap_instance.id) + + r = self._update_from_teamsnapdata(teamsnap_instance, api_response) + r = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance) + return r + + def _update_from_teamsnapdata(self, teamsnap_instance:teamsnap.models.TeamsnapBaseModel, teamsnap_data: teamsnap.teamsnap.api.ApiObject) -> teamsnap.models.TeamsnapBaseModel: + '''''' + if isinstance(teamsnap_data, teamsnap.teamsnap.api.ApiObject): + teamsnap_data = teamsnap_data.data + else: + raise TypeError + if not teamsnap_data['type'] == teamsnap_instance.type: + raise Exception() + data_type = teamsnap_data['type'] + fields = ['id', 'created_at', 'updated_at'] + related_objects = {} + if data_type in ['opponent', 'team', 'location']: + fields += ['name'] + elif data_type == 'event': + fields += [ + 'label', + 'start_date', + 'formatted_title', + 'points_for_opponent', + 'points_for_team', + 'is_game', + 'game_type' + ] + if teamsnap_data.get('location_id'): + related_objects['location'] = teamsnap.models.Location.objects.get(id=teamsnap_data['location_id']) + if teamsnap_data.get('opponent_id'): + related_objects['opponent'] = teamsnap.models.Opponent.objects.get(id=teamsnap_data['opponent_id']) + pass + + elif data_type == 'member': + fields += [ + 'first_name', + 'last_name', + 'jersey_number', + 'is_non_player' + ] + + elif data_type == 'availability': + fields += ['status_code'] + related_objects['member'] = teamsnap.models.Member.objects.get(id=teamsnap_data['member_id']) + related_objects['event'] = teamsnap.models.Event.objects.get(id=teamsnap_data['event_id']) + + else: + raise ValueError + + if teamsnap_data.get('team_id'): + related_objects['team'] = teamsnap.models.Team.objects.filter(id=teamsnap_data['team_id']).first() + + for field in fields: + value = teamsnap_data.get(field) + # if value is None: + # continue + # else: + setattr(teamsnap_instance,field,value) + + for related_object_name, related_object in related_objects.items(): + setattr(teamsnap_instance, related_object_name, related_object) + + teamsnap_instance.save() + + return teamsnap_instance + + def _update_teamsnapdb_to_benchcoachdb(self, teamsnap_instance, benchcoach_instance) -> List[Tuple[django.db.models.Model, bool]]: + + if isinstance(teamsnap_instance, teamsnap.models.Event): + benchcoach_model = Event + + d = { + 'start': teamsnap_instance.start_date, + } + + if teamsnap_instance.team: + if teamsnap_instance.team.benchcoach_object: + if teamsnap_instance.game_type == "Home": + d['home_team'] = teamsnap_instance.team.benchcoach_object + elif teamsnap_instance.game_type == "Away": + d['away_team'] = teamsnap_instance.team.benchcoach_object + elif not teamsnap_instance.team.benchcoach_object: + raise Team.DoesNotExist + + if teamsnap_instance.opponent: + if teamsnap_instance.opponent.benchcoach_object: + if teamsnap_instance.game_type == 'Home': + d['away_team'] = teamsnap_instance.opponent.benchcoach_object + elif teamsnap_instance.game_type == 'Away': + d['home_team'] = teamsnap_instance.opponent.benchcoach_object + elif not teamsnap_instance.opponent.benchcoach_object: + raise Team.DoesNotExist + pass + + if teamsnap_instance.location: + if teamsnap_instance.location.benchcoach_object: + if teamsnap_instance.location: + d['venue'] = teamsnap_instance.location.benchcoach_object + elif not teamsnap_instance.location.benchcoach_object: + raise Venue.DoesNotExist + + elif isinstance(teamsnap_instance, teamsnap.models.Opponent): + benchcoach_model = Team + d = { + 'name': teamsnap_instance.name, + } + + elif isinstance(teamsnap_instance, teamsnap.models.Team): + benchcoach_model = Team + d = { + 'name': teamsnap_instance.name, + } + + elif isinstance(teamsnap_instance, teamsnap.models.Location): + benchcoach_model = Venue + d = { + 'name': teamsnap_instance.name, + } + + elif isinstance(teamsnap_instance, teamsnap.models.Member): + benchcoach_model = Player + d = { + 'first_name': teamsnap_instance.first_name, + 'last_name': teamsnap_instance.last_name, + 'jersey_number': teamsnap_instance.jersey_number, + } + + elif isinstance(teamsnap_instance, teamsnap.models.Availability): + benchcoach_model = Availability + + translation = { + teamsnap_instance.YES: Availability.YES, + teamsnap_instance.NO: Availability.NO, + teamsnap_instance.MAYBE: Availability.MAYBE + } + + d = { + 'available': translation.get(teamsnap_instance.status_code, Availability.UNKNOWN), + 'player': teamsnap_instance.member.benchcoach_object, + 'event': teamsnap_instance.event.benchcoach_object + } + + r = [] + + if teamsnap_instance.member.benchcoach_object: + d['player'] = teamsnap_instance.member.benchcoach_object + elif not teamsnap_instance.member.benchcoach_object: + raise Player.DoesNotExist + + if teamsnap_instance.event.benchcoach_object: + d['event'] = teamsnap_instance.event.benchcoach_object + elif not teamsnap_instance.event.benchcoach_object: + raise Event.DoesNotExist + + else: + raise ValueError + + for field, value in d.items(): + setattr(benchcoach_instance, field, value) + + benchcoach_instance.save() + teamsnap_instance.benchcoach_object = benchcoach_instance + teamsnap_instance.save() + return benchcoach_instance + + def _find_counterpart(self, instance): + instance_type = type(instance) + counterpart_instance = None + if instance_type == Availability: + counterpart_instance = instance.teamsnap_availability + + elif instance_type == Player: + counterpart_instance = instance.teamsnap_member + + elif instance_type == Event: + counterpart_instance = instance.teamsnap_event + + elif instance_type == Venue: + counterpart_instance = instance.teamsnap_location + + elif instance_type == Team: + if hasattr(instance, 'teamsnap_opponent'): + counterpart_instance = instance.teamsnap_opponent + elif hasattr(instance, 'teamsnap_team'): + counterpart_instance = instance.teamsnap_team + else: + raise ValueError("instance doesn't seem to be an teamsnap opponent or a teamsnap team") + + elif instance_type == Positioning: + counterpart_instance = instance.teamsnap_lineupentry + + else: + raise Exception() + + return counterpart_instance + + def _sync_qs (self, qs, direction): + if qs.model not in self.models: + raise TypeError(f"Sync engine does not sync {qs.model} models") + + r=[] + r = self._bulk_sync_from_teamsnap(qs) + # for instance in qs: + # r += self._sync_instance(instance, direction=direction) + + return r + + def _sync_instance(self, instance, direction, data=None): + r=[] + if direction == 'download': + r.append(self._sync_from_teamsnap(instance)) + + elif direction == 'upload': + raise NotImplementedError('Uploading not supported by this sync engine yet.') + else: + raise TypeError(f"Direction {direction} not supported. 'upload' or 'download' must be specified") + + return r + + + def sync(self, qs: django.db.models.QuerySet = None, instance: django.db.models.Model = None, + direction='download') -> List[Tuple[django.db.models.Model, bool]]: + if not qs and not instance: + raise TypeError(f"sync requires either a QuerySet or model instance to be provided") + if qs and instance: + raise TypeError(f"sync requires either a QuerySet or model instance to be provided, but not both") + elif qs: + r = self._sync_qs(qs, direction) + elif instance: + r = self._sync_instance(instance, direction) + + return r + + def import_items(self, object_name=None, object_names=[]): + # order is important + + ['team', 'opponent', 'location', 'member', 'event', 'availability'] + kwargs = {'client':self.client,'team_id': self.managed_teamsnap_team_id} + r = {} + + # ---team--- + r['team'] = [] + for teamsnap_data in teamsnap.teamsnap.api.Team.search(client=self.client, id=self.managed_teamsnap_team_id): + if teamsnap.models.Team.objects.filter(id=teamsnap_data.data['id']): + teamsnap_instance = teamsnap.models.Team.objects.filter(id=teamsnap_data.data['id']).first() + benchcoach_instance = teamsnap_instance.benchcoach_object + else: + teamsnap_instance = teamsnap.models.Team() + benchcoach_instance = benchcoach.models.Team() + teamsnap_instance.benchcoach_object=benchcoach_instance + benchcoach_instance.save() + response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data) + teamsnap_instance.save() + response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance) + r['team'].append(response) + + # ---opponent--- + for teamsnap_data in teamsnap.teamsnap.api.Opponent.search(**kwargs): + if teamsnap.models.Opponent.objects.filter(id=teamsnap_data.data['id']): + teamsnap_instance = teamsnap.models.Opponent.objects.filter(id=teamsnap_data.data['id']).first() + benchcoach_instance = teamsnap_instance.benchcoach_object + else: + teamsnap_instance = teamsnap.models.Opponent() + benchcoach_instance = benchcoach.models.Team() + teamsnap_instance.benchcoach_object = benchcoach_instance + benchcoach_instance.save() + response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data) + response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance) + r['team'].append(response) + + # ---location--- + r['location'] = [] + for teamsnap_data in teamsnap.teamsnap.api.Location.search(**kwargs): + if teamsnap.models.Location.objects.filter(id=teamsnap_data.data['id']): + teamsnap_instance = teamsnap.models.Location.objects.filter(id=teamsnap_data.data['id']).first() + benchcoach_instance = teamsnap_instance.benchcoach_object + else: + teamsnap_instance = teamsnap.models.Location() + benchcoach_instance = benchcoach.models.Venue() + teamsnap_instance.benchcoach_object = benchcoach_instance + benchcoach_instance.save() + + response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data) + response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance) + r['location'].append(response) + + # ---member--- + # Note: Non players not included in sync. + r['member'] = [] + for teamsnap_data in teamsnap.teamsnap.api.Member.search(**kwargs, + is_non_player = False + ): + if teamsnap_data.data['is_non_player'] == True: + continue + if teamsnap.models.Member.objects.filter(id=teamsnap_data.data['id']): + teamsnap_instance = teamsnap.models.Member.objects.filter(id=teamsnap_data.data['id']).first() + benchcoach_instance = teamsnap_instance.benchcoach_object + else: + teamsnap_instance = teamsnap.models.Member() + benchcoach_instance = benchcoach.models.Player() + teamsnap_instance.benchcoach_object = benchcoach_instance + benchcoach_instance.save() + + response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data) + response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance) + r['member'].append(response) + + # ---event--- + r['event'] = [] + for teamsnap_data in teamsnap.teamsnap.api.Event.search(**kwargs): + if teamsnap.models.Event.objects.filter(id=teamsnap_data.data['id']): + teamsnap_instance = teamsnap.models.Event.objects.filter(id=teamsnap_data.data['id']).first() + benchcoach_instance = teamsnap_instance.benchcoach_object + else: + teamsnap_instance = teamsnap.models.Event() + benchcoach_instance = benchcoach.models.Event() + teamsnap_instance.benchcoach_object = benchcoach_instance + benchcoach_instance.save() + + response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data) + response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance) + r['event'].append(response) + + # ---availability--- + # Note: Non players not included in sync + r['availability'] = [] + 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, + member_id=",".join(player_ids) + ): + if teamsnap.models.Availability.objects.filter(id=teamsnap_data.data['id']): + teamsnap_instance = teamsnap.models.Availability.objects.filter(id=teamsnap_data.data['id']).first() + benchcoach_instance = teamsnap_instance.benchcoach_object + else: + teamsnap_instance = teamsnap.models.Availability() + event_instance = benchcoach.models.Event.objects.get(teamsnap_event__id=teamsnap_data.data['event_id']) + player_instance = benchcoach.models.Player.objects.get(teamsnap_member__id=teamsnap_data.data['member_id']) + benchcoach_instance = benchcoach.models.Availability(event=event_instance, player=player_instance) + benchcoach_instance.save() + teamsnap_instance.benchcoach_object_id = benchcoach_instance.id + response = self._update_from_teamsnapdata(teamsnap_instance, teamsnap_data) + response = self._update_teamsnapdb_to_benchcoachdb(teamsnap_instance, benchcoach_instance) + r['availability'].append(response) + + return r \ No newline at end of file diff --git a/teamsnap/utils/test_sync.py b/teamsnap/utils/test_sync.py new file mode 100644 index 0000000..bf5a3b2 --- /dev/null +++ b/teamsnap/utils/test_sync.py @@ -0,0 +1,51 @@ +from django.test import TestCase +import os + +from teamsnap.utils.teamsnap_sync_engine import TeamsnapSyncEngine + +import benchcoach.models +import teamsnap.models + +TEAMSNAP_TOKEN = os.environ['TEAMSNAP_TOKEN'] +TEAM_TEAMSNAP_ID = os.environ['TEAM_TEAMSNAP_ID'] + +pass + +# syncengine = TeamsnapSyncEngine(managed_team_teamsnap_id=TEAM_TEAMSNAP_ID, teamsnap_token=TEAMSNAP_TOKEN) +# r = syncengine.import_items() + +class TestSync(TestCase): + fixtures = ['minimal'] + + def setUp(self): + self.syncengine = TeamsnapSyncEngine(managed_team_teamsnap_id=TEAM_TEAMSNAP_ID, teamsnap_token=TEAMSNAP_TOKEN) + r = self.syncengine.import_items() + pass + + def test_syncengine(self): + # test that the import can be run again + r = self.syncengine.import_items() + benchcoach_objects = { + 'availability': list(benchcoach.models.Availability.objects.all()), + 'event': list(benchcoach.models.Event.objects.all()), + 'player': list(benchcoach.models.Player.objects.all()), + 'positioning': list(benchcoach.models.Positioning.objects.all()), + 'team': list(benchcoach.models.Team.objects.all()), + 'venue': list(benchcoach.models.Venue.objects.all()) + } + teamsnap_objects = { + 'availability': list(teamsnap.models.Availability.objects.all()), + 'event': list(teamsnap.models.Event.objects.all()), + 'member': list(teamsnap.models.Member.objects.all()), + 'lineupentry': list(teamsnap.models.LineupEntry.objects.all()), + 'team': list(teamsnap.models.Team.objects.all()), + 'opponent': list(teamsnap.models.Opponent.objects.all()), + 'location': list(teamsnap.models.Location.objects.all()) + } + self.assertIsNotNone(r) + + def test_all_models(self): + pass + self.syncengine.sync(qs=benchcoach.models.Event.objects.all()) + breakpoint() + pass \ No newline at end of file diff --git a/teamsnap/views.py b/teamsnap/views.py index 615978d..da6e248 100644 --- a/teamsnap/views.py +++ b/teamsnap/views.py @@ -45,7 +45,7 @@ def home(request): (Member, benchcoach.models.Player), (Opponent, benchcoach.models.Team), (Team, benchcoach.models.Team), - (User, None) + # (User, {'name':}) ]: teamsnap_objects[teamsnap_obj.__name__.lower()] = {} teamsnap_objects[teamsnap_obj.__name__.lower()]['object_count'] = teamsnap_obj.objects.count() @@ -183,41 +183,37 @@ def send_to_benchcoach(request, object_name): }.get(object_name) TEAM_ID = request.user.profile.teamsnapsettings.managed_team.id + TOKEN = request.user.profile.teamsnap_access_token + + sync_engine = TeamsnapSyncEngine(teamsnap_token=TOKEN, managed_team_teamsnap_id=TEAM_ID) r = {} r[object_name]=[] if object_name == 'team': - for team in Object.objects.filter(id=TEAM_ID): - r[object_name] += update_opponent(team, create_benchcoach_object=True, create_related=True) + r[object_name] = sync_engine.sync(qs=benchcoach.models.Team.objects.all()) - if object_name == 'opponent': - for team in Object.objects.filter(team_id=TEAM_ID): - r[object_name] += update_team(team, create_benchcoach_object=True, create_related=True) + if object_name == 'venue': + r[object_name] = sync_engine.sync(qs=benchcoach.models.Venue.objects.all()) + pass - if object_name == 'location': - for location in Location.objects.filter(team_id=TEAM_ID): - r[object_name] += update_location(location, create_benchcoach_object=True, create_related=True) - - if object_name == 'member': - for member in Member.objects.filter(team_id=TEAM_ID, is_non_player=False): - r[object_name] += update_member(member, create_benchcoach_object=True, create_related=True) + if object_name == 'player': + r[object_name] = sync_engine.sync(qs=benchcoach.models.Player.objects.all()) if object_name == 'event': - for event in Event.objects.filter(team_id=TEAM_ID): - r[object_name] += update_event(event, create_benchcoach_object=True, create_related=True) + r[object_name] = sync_engine.sync(qs=benchcoach.models.Event.objects.all()) + pass if object_name == 'availability': - for availability in Availability.objects.filter(team_id=TEAM_ID, member__is_non_player=False): - r[object_name] += update_availability(availability, create_benchcoach_object=True, create_related=True) + r[object_name] = [] + for event in benchcoach.models.Player.objects.all(): + r[object_name] += sync_engine.sync(qs=event.availability_set.all()) for object_name, results in r.items(): - if len(r) == 0: - messages.error(request, f"Error! No {object_name} objects created or updated") + if len(results) == 0: + messages.error(request, f"Error! No {object_name} objects updated") else: - result = [created for obj, created in results] - messages.success(request, - f"Success! {sum(result)} {object_name} objects created, {len(result) - sum(result)} {object_name} objects updated.") + messages.success(request, f"Success! {len(results)} {object_name} objects updated.") return redirect('teamsnap home') @@ -304,8 +300,21 @@ def sync_teamsnapdb_to_benchcoachdb(request): } return JsonResponse(data) - - +from .utils.teamsnap_sync_engine import TeamsnapSyncEngine +def import_teamsnap(request): + TEAM_ID = request.user.profile.teamsnapsettings.managed_team.id + TOKEN = request.user.profile.teamsnap_access_token + + sync_engine = TeamsnapSyncEngine(teamsnap_token=TOKEN, managed_team_teamsnap_id=TEAM_ID) + r = sync_engine.import_items() + + for object_name, results in r.items(): + if len(results) == 0: + messages.error(request, f"Error! No {object_name} objects created or updated") + else: + messages.success(request, f"Success! {len(results)} {object_name} objects imported") + + return redirect('teamsnap home')
{{ obj_data.object_count }} {{ obj_data.counterpart.name }} {{ obj_data.counterpart.object_count }} - - - + + Sync