Private
Public Access
1
0

polar passing tests

This commit is contained in:
Sander Roosendaal
2023-02-18 17:16:54 +01:00
parent 3373081b42
commit 3420da47d7
9 changed files with 565 additions and 110 deletions

View File

@@ -4,7 +4,7 @@ from .nk import NKIntegration
from .sporttracks import SportTracksIntegration from .sporttracks import SportTracksIntegration
from .rp3 import RP3Integration from .rp3 import RP3Integration
from .trainingpeaks import TPIntegration from .trainingpeaks import TPIntegration
from .polar import PolarIntegration
importsources = { importsources = {
'c2': C2Integration, 'c2': C2Integration,
@@ -14,5 +14,6 @@ importsources = {
'nk': NKIntegration, 'nk': NKIntegration,
'tp':TPIntegration, 'tp':TPIntegration,
'rp3':RP3Integration, 'rp3':RP3Integration,
'polar': PolarIntegration
} }

View File

@@ -0,0 +1,521 @@
from rowers.rower_rules import ispromember
from .integrations import SyncIntegration
from rowers.models import User, Rower, Workout
from rowsandall_app.settings import (
POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET, UPLOAD_SERVICE_URL
)
import urllib
import requests
from rowers.utils import dologging, myqueue, NoTokenError
from django.utils import timezone
from uuid import uuid4
import base64
from rowers.tasks import handle_request_post
from json.decoder import JSONDecodeError
from rowers.opaque import encoder
import rowers.mytypes as mytypes
from django.conf import settings
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
baseurl = 'https://polaraccesslink.com/v3'
class PolarIntegration(SyncIntegration):
def __init__(self, *args, **kwargs):
if args[0] is not None:
super(PolarIntegration, self).__init__(*args, **kwargs)
def get_notifications(self):
url = baseurl+'/notifications'
# state = str(uuid4())
auth_string = '{id}:{secret}'.format(
id=POLAR_CLIENT_ID,
secret=POLAR_CLIENT_SECRET
)
try:
headers = {'Authorization': 'Basic %s' % base64.b64encode(auth_string)}
except TypeError:
headers = {'Authorization': 'Basic %s' % base64.b64encode(
bytes(auth_string, 'utf-8')).decode('utf-8')}
try:
response = requests.get(url, headers=headers)
except ConnectionError: # pragma: no cover
response = {
'status_code': 400,
}
available_data = []
try:
if response.status_code == 200:
available_data = response.json()['available-user-data']
dologging('polar.log', available_data)
else: # pragma: no cover
dologging('polar.log', response.status_code)
dologging('polar.log', response.text)
except AttributeError: # pragma: no cover
try:
dologging('polar.log', response.text)
except AttributeError:
pass
pass
return available_data
def get_name(self):
return "Polar Flow"
def get_shortname(self):
raise "polar"
def createworkoutdata(self, w, *args, **kwargs):
raise NotImplementedError
def workout_export(self, workout, *args, **kwargs) -> str:
raise NotImplementedError
def revoke_access(self): # pragma: no cover
user = self.user
headers = {
'Authorization': 'Bearer {token}'.format(token=user.rower.polartoken)
}
response = requests.delete('https://www.polaraccesslink.com/v3/users/{userid}'.format(
userid=user.rower.polaruserid
), headers=headers)
dologging('polar.log', response.text)
dologging('polar.log', response.reason)
return 1
def get_polar_workouts(self, user):
r = Rower.objects.get(user=user)
exercise_list = []
if (r.polartoken == '') or (r.polartoken is None):
s = "Token doesn't exist. Need to authorize"
return []
elif (timezone.now() > r.polartokenexpirydate): # pragma: no cover
s = "Token expired. Needs to refresh"
dologging('polar.log', s)
return []
authorizationstring = str('Bearer ' + r.polartoken)
headers = {'Authorization': authorizationstring,
'Accept': 'application/json'}
headers2 = {
'Authorization': authorizationstring,
}
url = baseurl+'/users/{userid}/exercise-transactions'.format(
userid=r.polaruserid
)
response = requests.post(url, headers=headers)
dologging('polar.log', url)
dologging('polar.log', authorizationstring)
dologging('polar.log', str(response.status_code))
if response.status_code == 201:
transactionid = response.json()['transaction-id']
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
dologging('polar.log', url)
response = requests.get(url, headers=headers)
if response.status_code == 200:
exerciseurls = response.json()['exercises']
dologging('polar.log', exerciseurls)
for exerciseurl in exerciseurls:
response = requests.get(exerciseurl, headers=headers)
if response.status_code == 200:
exercise_dict = response.json()
tcxuri = exerciseurl+'/tcx'
response = requests.get(tcxuri, headers=headers2)
if response.status_code == 200:
filename = 'media/mailbox_attachments/{code}_{id}.tcx'.format(
id=exercise_dict['id'],
code=uuid4().hex[:16]
)
dologging('polar.log', filename)
with open(filename, 'wb') as fop:
fop.write(response.content)
workouttype = 'other'
try:
workouttype = mytypes.polaraccesslink_sports[
exercise_dict['detailed-sport-info']]
except KeyError: # pragma: no cover
dologging(
'polar.log', exercise_dict['detailed-sport-info'])
dologging('polar.log', workouttype)
try:
workouttype = mytypes.polarmappinginv[exercise_dict['sport'].lower(
)]
except KeyError:
dologging('polar.log', workouttype)
pass
dologging('polar.log', workouttype)
# post file to upload api
# TODO: add workouttype
uploadoptions = {
'title': '',
'workouttype': workouttype,
'boattype': '1x',
'user': user.id,
'secret': settings.UPLOAD_SERVICE_SECRET,
'file': filename,
'title': '',
}
url = settings.UPLOAD_SERVICE_URL
dologging('polar.log', uploadoptions)
dologging('polar.log', url)
_ = myqueue(
queuehigh,
handle_request_post,
url,
uploadoptions
)
dologging('polar.log', response.status_code)
if response.status_code != 200: # pragma: no cover
try:
dologging('polar.log', response.text)
except:
pass
try:
dologging('polar.log', response.json())
except:
pass
exercise_dict['filename'] = filename
else: # pragma: no cover
exercise_dict['filename'] = ''
exercise_list.append(exercise_dict)
dologging('polar.log', str(exercise_dict))
# commit transaction
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
requests.put(url, headers=headers)
dologging(
'polar.log', 'Committed transation at {url}'.format(url=url))
return exercise_list
def get_workouts(self, *args, **kwargs) -> int:
available_data = self.get_notifications()
polaruserid = self.rower.polaruserid
for record in available_data:
dologging('polar.log', str(record))
if record['data-type'] == 'EXERCISE':
try:
r = Rower.objects.get(polaruserid=record['user-id'])
u = r.user
if r.polar_auto_import and ispromember(u):
exercise_list = self.get_polar_workouts(u)
dologging('polar.log', exercise_list)
elif record['user-id'] == polaruserid:
exercise_list = self.get_polar_workouts(u)
except Rower.DoesNotExist: # pragma: no cover
pass
return 1
def register_user(self, token):
_ = self.open()
authorizationstring = 'Bearer {token}'.format(token=token)
headers = {
'Content-Type': 'application/xml',
'Authorization': authorizationstring,
'Accept': 'application/json'
}
payload = {
"member-id": encoder.encode_hex(self.user.id)
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer {token}'.format(token=token)
}
dologging('polar.log', 'Registering user')
response = requests.post(
'https://www.polaraccesslink.com/v3/users',
json=payload,
headers=headers
)
if response.status_code not in [200, 201]: # pragma: no cover
# dologging('polar.log',url)
dologging('polar.log', headers)
dologging('polar.log', payload)
dologging('polar.log', response.status_code)
dologging('polar.log', response.content)
try:
dologging('polar.log', response.reason)
dologging('polar.log', response.text)
except KeyError:
pass
try:
jsondata = response.json()
if jsondata['error']['error_type'] == 'user_already_registered':
return jsondata
except:
pass
return {}
polar_user_data = response.json()
return polar_user_data
def get_polar_user_info(self, physical=False): # pragma: no cover
r = self.rower
_ = self.open()
authorizationstring = str('Bearer ' + r.polartoken)
headers = {
'Authorization': authorizationstring,
'Accept': 'application/json'
}
if not physical:
url = baseurl+'/users/{userid}'.format(
userid=r.polaruserid
)
else:
url = 'https://www.polaraccesslink.com/v3/users/{userid}/physical-information-transactions/'.format(
userid=r.polaruserid
)
if physical:
response = requests.post(url, headers=headers)
else:
response = requests.get(url, headers=headers)
return response
def get_workout(self, id, transaction_id) -> int:
r = self.rower
_ = self.open()
authorizationstring = str('Bearer ' + r.polartoken)
headers = {
'Authorization': authorizationstring,
'Accept': 'application/json'
}
url = baseurl+'/users/{userid}/exercise-transactions'.format(
userid=r.polaruserid
)
response = requests.post(url, headers=headers)
if response.status_code == 201:
transactionid = response.json()['transaction-id']
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
response = requests.get(url, headers=headers)
if response.status_code == 200:
exerciseurls = response.json()['exercises']
for exerciseurl in exerciseurls:
response = requests.get(exerciseurl, headers=headers)
if response.status_code == 200:
exercise_dict = response.json()
thisid = exercise_dict['id']
if thisid == id:
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}' \
'/exercises/{exerciseid}/tcx'.format(
userid=r.polaruserid,
transactionid=transactionid,
exerciseid=id)
authorizationstring = str('Bearer ' + r.polartoken)
headers2 = {
'Authorization': authorizationstring,
}
response = requests.get(url, headers=headers2)
if response.status_code == 200:
result = response.content
# commit transaction
url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format(
transactionid=transactionid,
userid=r.polaruserid
)
response = requests.put(url, headers=headers)
dologging(
'polar.log', 'Committing transaction on {url}'.format(url=url))
else: # pragma: no cover
result = None
return result
def get_workout_list(self, *args, **kwargs) -> list:
exercises = self.get_polar_workouts(self.user)
workouts = []
try:
a = exercises.status_code
return []
except:
return []
for exercise in exercises:
try:
d = exercise['distance']
except KeyError:
d = 0
i = exercise['id']
transactionid = exercise['transaction-id']
starttime = exercise['start-time']
rowtype = exercise['sport']
durationstring = exercise['duration']
duration = isodate.parse_duration(durationstring)
keys = ['id', 'distance', 'duration',
'starttime', 'rowtype', 'source', 'name', 'new']
values = [i, d, duration, starttime, rowtype, transactionid, '', '']
res = dict(zip(keys, values))
workouts.append(res)
return workouts
def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover
state = str(uuid4())
params = {"client_id": POLAR_CLIENT_ID,
"response_type": "code",
# "redirect_uri": POLAR_REDIRECT_URI,
"state": state,
# "scope":"accesslink.read_all"
}
url = "https://flow.polar.com/oauth2/authorization?" + \
urllib.parse.urlencode(params)
dologging('polar.log', 'Authorizing')
dologging('polar.log', url)
dologging('polar.log', params)
return url
def get_token(self, code, *args, **kwargs) -> (str, int, str):
post_data = {"grant_type": "authorization_code",
"code": code,
# "redirect_uri": POLAR_REDIRECT_URI,
}
auth_string = '{id}:{secret}'.format(
id=POLAR_CLIENT_ID,
secret=POLAR_CLIENT_SECRET
)
try:
headers = {'Authorization': 'Basic %s' % base64.b64encode(auth_string)}
except TypeError:
headers = {'Authorization': 'Basic %s' % base64.b64encode(
bytes(auth_string, 'utf-8')).decode('utf-8')}
dologging('polar.log', 'Getting token')
dologging('polar.log', post_data)
dologging('polar.log', auth_string)
response = requests.post("https://polarremote.com/v2/oauth2/token",
data=post_data,
headers=headers)
if response.status_code != 200: # pragma: no cover
dologging('polar.log', 'Getting token, got:')
dologging('polar.log', response.status_code)
dologging('polar.log', response.reason)
dologging('polar.log', response.text)
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
user_id = token_json['x_user_id']
dologging('polar.log', response.status_code)
try:
dologging('polar.log', response.text)
except AttributeError:
pass
dologging('polar.log', token_json)
except (KeyError, JSONDecodeError) as e: # pragma: no cover
dologging('polar.log', e)
try:
dologging('polar.log', response.text)
except AttributeError:
pass
thetoken = 0
expires_in = 0
user_id = 0
return [thetoken, expires_in, user_id]
def open(self, *args, **kwargs) -> str:
r = self.rower
if (r.polartoken == '') or (r.polartoken is None):
s = "Token doesn't exist. Need to authorize"
raise NoTokenError(s)
elif (timezone.now() > r.polartokenexpirydate):
s = "Token expired. Needs to refresh"
raise NoTokenError(s)
token = self.rower.polartoken
return token
def token_refresh(self, *args, **kwargs) -> str:
raise NotImplementedError
# just as a quick test during development
u = User.objects.get(id=1)
nk_integration_1 = PolarIntegration(u)

View File

@@ -322,10 +322,4 @@ class SportTracksIntegration(SyncIntegration):
def token_refresh(self, *args, **kwargs) -> str: def token_refresh(self, *args, **kwargs) -> str:
return super(SportTracksIntegration, self).token_refresh(*args, **kwargs) return super(SportTracksIntegration, self).token_refresh(*args, **kwargs)
# just as a quick test during development
u = User.objects.get(id=1)
nk_integration_1 = SportTracksIntegration(u)

View File

@@ -27,9 +27,6 @@ from rowingdata import rowingdata as rrdata
import rowers.uploads as uploads import rowers.uploads as uploads
import rowers.polarstuff as polarstuff
from rowers.opaque import encoder from rowers.opaque import encoder
from rowers.integrations import * from rowers.integrations import *
from rowers.rower_rules import user_is_not_basic, user_is_coachee from rowers.rower_rules import user_is_not_basic, user_is_coachee
@@ -77,8 +74,9 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
# Polar # Polar
try: try:
polar_available = polarstuff.get_polar_notifications() polarintegration = PolarIntegration(None)
_ = polarstuff.get_all_new_workouts(polar_available)
_ = polarintegration.get_workouts()
except: # pragma: no cover except: # pragma: no cover
exc_type, exc_value, exc_traceback = sys.exc_info() exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback) lines = traceback.format_exception(exc_type, exc_value, exc_traceback)

View File

@@ -15,7 +15,6 @@ import rowers
from rowers import dataprep from rowers import dataprep
from rowers import tasks from rowers import tasks
from rowers import polarstuff
import urllib import urllib
import json import json
@@ -875,39 +874,40 @@ class PolarObjects(DjangoTestCase):
csvfilename=filename csvfilename=filename
) )
@patch('rowers.polarstuff.requests.post', side_effect=mocked_requests) @patch('rowers.integrations.polar.requests.post', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.polar.requests.get', side_effect=mocked_requests)
def test_polar_auto_import(self, mock_get, mock_post): def test_polar_auto_import(self, mock_get, mock_post):
self.r.polar_auto_import = True self.r.polar_auto_import = True
self.r.save() self.r.save()
integration = PolarIntegration(self.r.user)
res = integration.get_workouts(self.r.user)
self.assertEqual(res,1)
res = polarstuff.get_polar_workouts(self.r.user) @patch('rowers.integrations.polar.requests.post', side_effect=mocked_requests)
self.assertEqual(len(res),2) @patch('rowers.integrations.polar.requests.get', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.post', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.get', side_effect=mocked_requests)
def test_polar_callback(self, mock_get, mock_post): def test_polar_callback(self, mock_get, mock_post):
response = self.c.get('/polarflowcallback?code=abcdef&state=12sdss',follow=True) response = self.c.get('/polarflowcallback?code=abcdef&state=12sdss',follow=True)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
@patch('rowers.polarstuff.requests.post', side_effect=mocked_requests) @patch('rowers.integrations.polar.requests.post', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.polar.requests.get', side_effect=mocked_requests)
def test_polar_notifications(self, mock_get, mock_post): def test_polar_notifications(self, mock_get, mock_post):
data = polarstuff.get_polar_notifications() integration = PolarIntegration(self.r.user)
data = integration.get_notifications()
self.assertEqual(data[0]['user-id'],475) self.assertEqual(data[0]['user-id'],475)
response = polarstuff.get_all_new_workouts(data) response = integration.get_workouts()
self.assertEqual(response,1) self.assertEqual(response,1)
@patch('rowers.polarstuff.requests.post', side_effect=mocked_requests) @patch('rowers.integrations.polar.requests.post', side_effect=mocked_requests)
@patch('rowers.polarstuff.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.polar.requests.get', side_effect=mocked_requests)
def test_polar_get_workout(self, mock_get, mock_post): def test_polar_get_workout(self, mock_get, mock_post):
transaction_id = 240522162 transaction_id = 240522162
id = 1937529874 id = 1937529874
integration = PolarIntegration(self.u)
response = polarstuff.get_polar_workout(self.u, id, transaction_id) response = integration.get_workout(id, transaction_id)
self.assertEqual(len(response),14836) self.assertEqual(len(response),14836)

Binary file not shown.

View File

@@ -624,10 +624,6 @@ urlpatterns = [
views.workout_import_view, name='workout_import_view'), views.workout_import_view, name='workout_import_view'),
re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/$', re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/$',
views.workout_getimportview, name='workout_getimportview'), views.workout_getimportview, name='workout_getimportview'),
re_path(r'^workout/polarimport/$', views.workout_polarimport_view,
name='workout_polarimport_view'),
re_path(r'^workout/polarimport/user/(?P<userid>\d+)/',
views.workout_polarimport_view, name='workout_polarimport_view'),
re_path(r'^workout/(?P<id>[0-9A-Fa-f]+)/(?P<source>[0-9A-za-z]+)uploadw/$', re_path(r'^workout/(?P<id>[0-9A-Fa-f]+)/(?P<source>[0-9A-za-z]+)uploadw/$',
views.workout_export_view, name='workout_export_view'), views.workout_export_view, name='workout_export_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/recalcsummary/$', re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/recalcsummary/$',

View File

@@ -76,21 +76,8 @@ def rower_garmin_authorize(request): # pragma: no cover
# Polar Authorization # Polar Authorization
@login_required() @login_required()
def rower_polar_authorize(request): # pragma: no cover def rower_polar_authorize(request): # pragma: no cover
integration = importsources['polar'](request.user)
state = str(uuid4()) url = integration.make_authorization_url()
params = {"client_id": POLAR_CLIENT_ID,
"response_type": "code",
# "redirect_uri": POLAR_REDIRECT_URI,
"state": state,
# "scope":"accesslink.read_all"
}
url = "https://flow.polar.com/oauth2/authorization?" + \
urllib.parse.urlencode(params)
dologging('polar.log', 'Authorizing')
dologging('polar.log', url)
dologging('polar.log', params)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@login_required() @login_required()
@@ -166,8 +153,9 @@ def rower_process_twittercallback(request): # pragma: no cover
@login_required() @login_required()
def rower_process_polarcallback(request): def rower_process_polarcallback(request):
integration = importsources['polar'](request.user)
error = request.GET.get('error', 'no error') error = request.GET.get('error', 'no error')
dologging('polar.log', 'Callback: {error}'.format(error=error)) dologging('polar.log', 'Callback: {error}'.format(error=error))
if error != 'no error': # pragma: no cover if error != 'no error': # pragma: no cover
messages.error(request, error) messages.error(request, error)
@@ -188,7 +176,7 @@ def rower_process_polarcallback(request):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
access_token, expires_in, user_id = polarstuff.get_token(code) access_token, expires_in, user_id = integration.get_token(code)
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = getrower(request.user) r = getrower(request.user)
r.polartoken = access_token r.polartoken = access_token
@@ -198,12 +186,28 @@ def rower_process_polarcallback(request):
r.save() r.save()
if user_id: if user_id:
polar_user_data = polarstuff.register_user(request.user, access_token) polar_user_data = integration.register_user(access_token)
else: # pragma: no cover else: # pragma: no cover
messages.error(request, 'Polar Flow Authorization Failed') messages.error(request, 'Polar Flow Authorization Failed')
url = reverse('rower_exportsettings_view') url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
try:
error = polar_user_data['error']
if error['error_type'] == 'user_already_registered':
s = error['message']
tester = re.compile(r'.*userid:(?P<id>\d+)')
testresult = tester.match(s)
if testresult:
user_id2 = testresult.group('id')
if user_id2 == str(user_id):
messages.info(request,
"Tokens stored. Good to go. Please check your import/export settings")
url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url)
except KeyError:
pass
try: try:
user_id2 = polar_user_data['polar-user-id'] user_id2 = polar_user_data['polar-user-id']
except KeyError: # pragma: no cover except KeyError: # pragma: no cover
@@ -942,64 +946,6 @@ def garmin_details_view(request):
return HttpResponse(status=200) return HttpResponse(status=200)
# the page where you select which Polar workout to Import
@login_required()
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
def workout_polarimport_view(request, userid=0): # pragma: no cover
exercises = polarstuff.get_polar_workouts(request.user)
workouts = []
try:
a = exercises.status_code
if a == 401:
messages.error(
request, 'Not authorized. You need to connect to Polar first')
url = reverse('workouts_view')
return HttpResponseRedirect(url)
except: # pragma: no cover
exercises = []
pass
for exercise in exercises:
try:
d = exercise['distance']
except KeyError:
d = 0
i = exercise['id']
transactionid = exercise['transaction-id']
starttime = exercise['start-time']
rowtype = exercise['sport']
durationstring = exercise['duration']
duration = isodate.parse_duration(durationstring)
keys = ['id', 'distance', 'duration',
'starttime', 'type', 'transactionid']
values = [i, d, duration, starttime, rowtype, transactionid]
res = dict(zip(keys, values))
workouts.append(res)
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': reverse('workout_polarimport_view'),
'name': 'Polar'
},
]
r = getrower(request.user)
return render(request, 'polar_list_import.html',
{
'workouts': workouts,
'active': 'nav-workouts',
'rower': r,
'breadcrumbs': breadcrumbs,
'teams': get_my_teams(request.user),
})
importauthorizeviews = { importauthorizeviews = {
'c2': 'rower_integration_authorize', 'c2': 'rower_integration_authorize',

View File

@@ -200,7 +200,6 @@ import rowers.rojabo_stuff as rojabo_stuff
import rowers.garmin_stuff as garmin_stuff import rowers.garmin_stuff as garmin_stuff
from rowers.rojabo_stuff import rojabo_open from rowers.rojabo_stuff import rojabo_open
import rowers.polarstuff as polarstuff
from rowers.integrations import * from rowers.integrations import *