Skip to content

Commit

Permalink
feat(socialaccount): Add provider TrainingPeaks
Browse files Browse the repository at this point in the history
* Add TrainingPeaks provider

* Add convenience method api_hostname

* Add response.raise_for_status()

* Rename TrainingPeaksOAuth2Adapter
  • Loading branch information
squio authored Jun 5, 2021
1 parent 9f52b43 commit c50a30a
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 0 deletions.
Empty file.
46 changes: 46 additions & 0 deletions allauth/socialaccount/providers/trainingpeaks/provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from allauth.socialaccount.providers.base import ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider


class TrainingPeaksAccount(ProviderAccount):
def get_profile_url(self):
return "https://app.trainingpeaks.com"

def get_avatar_url(self):
return None

def to_str(self):
name = self.account.extra_data.get("FirstName") + \
" " + self.account.extra_data.get("LastName")
if name != " ":
return name
return super(TrainingPeaksAccount, self).to_str()

class TrainingPeaksProvider(OAuth2Provider):
id = "trainingpeaks"
name = "TrainingPeaks"
account_class = TrainingPeaksAccount

def extract_uid(self, data):
return data.get("Id")

def extract_common_fields(self, data):
extra_common = super(TrainingPeaksProvider, self).extract_common_fields(data)
firstname = data.get("FirstName")
lastname = data.get("LastName")
# fallback username as there is actually no Username in response
username = firstname.strip().lower() + "." + lastname.strip().lower()
name = " ".join(part for part in (firstname, lastname) if part)
extra_common.update(
username=data.get("username", username),
email=data.get("Email"),
first_name=firstname,
last_name=lastname,
name=name.strip(),
)
return extra_common

def get_default_scope(self):
return ["athlete:profile"]

provider_classes = [TrainingPeaksProvider]
85 changes: 85 additions & 0 deletions allauth/socialaccount/providers/trainingpeaks/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
"""
Run just this suite:
python manage.py test allauth.socialaccount.providers.trainingpeaks.tests.TrainingPeaksTests
"""
from __future__ import unicode_literals

from collections import namedtuple

from django.contrib.auth.models import User
from django.test.utils import override_settings

from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase

from .provider import TrainingPeaksProvider
from .views import TrainingPeaksOAuth2Adapter


class TrainingPeaksTests(OAuth2TestsMixin, TestCase):
provider_id = TrainingPeaksProvider.id

def get_mocked_response(self):
return MockedResponse(
200,
"""{
"Id": 123456,
"FirstName": "John",
"LastName": "Doe",
"Email": "[email protected]",
"DateOfBirth": "1986-02-01T00:00:00",
"CoachedBy": 987654,
"Weight": 87.5223617553711
}""",
) # noqa

def get_login_response_json(self, with_refresh_token=True):
rtoken = ""
if with_refresh_token:
rtoken = ',"refresh_token": "testrf"'
return (
"""{
"access_token" : "testac",
"token_type" : "bearer",
"expires_in" : 600,
"scope": "scopes granted"
%s }"""
% rtoken
)

def test_default_use_sandbox_uri(self):
adapter = TrainingPeaksOAuth2Adapter(None)
self.assertTrue('.sandbox.' in adapter.authorize_url)
self.assertTrue('.sandbox.' in adapter.access_token_url)
self.assertTrue('.sandbox.' in adapter.profile_url)

@override_settings(SOCIALACCOUNT_PROVIDERS={
'trainingpeaks': {
'USE_PRODUCTION': True
}
})
def test_use_production_uri(self):
adapter = TrainingPeaksOAuth2Adapter(None)
self.assertFalse('.sandbox.' in adapter.authorize_url)
self.assertFalse('.sandbox.' in adapter.access_token_url)
self.assertFalse('.sandbox.' in adapter.profile_url)

def test_scope_from_default(self):
Request = namedtuple('request', ['GET'])
mock_request = Request(GET={})
scope = self.provider.get_scope(mock_request)
self.assertTrue('athlete:profile' in scope)

@override_settings(SOCIALACCOUNT_PROVIDERS={
'trainingpeaks': {
'SCOPE': ['athlete:profile', 'workouts', 'workouts:wod']
}
})
def test_scope_from_settings(self):
Request = namedtuple('request', ['GET'])
mock_request = Request(GET={})
scope = self.provider.get_scope(mock_request)
for item in ('athlete:profile', 'workouts', 'workouts:wod'):
self.assertTrue(item in scope)
6 changes: 6 additions & 0 deletions allauth/socialaccount/providers/trainingpeaks/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns

from .provider import TrainingPeaksProvider


urlpatterns = default_urlpatterns(TrainingPeaksProvider)
59 changes: 59 additions & 0 deletions allauth/socialaccount/providers/trainingpeaks/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import requests

from allauth.socialaccount import app_settings
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)

from .provider import TrainingPeaksProvider


class TrainingPeaksOAuth2Adapter(OAuth2Adapter):
# https://github.com/TrainingPeaks/PartnersAPI/wiki/OAuth
provider_id = TrainingPeaksProvider.id

def get_settings(self):
""" Provider settings """
return app_settings.PROVIDERS.get(self.provider_id, {})

def get_hostname(self):
""" Return hostname depending on sandbox seting """
settings = self.get_settings()
if (settings.get('USE_PRODUCTION')):
return 'trainingpeaks.com'
return 'sandbox.trainingpeaks.com'

@property
def access_token_url(self):
return "https://oauth." + self.get_hostname() + "/oauth/token"

@property
def authorize_url(self):
return "https://oauth." + self.get_hostname() + "/OAuth/Authorize"

@property
def profile_url(self):
return "https://api." + self.get_hostname() + "/v1/athlete/profile"

@property
def api_hostname(self):
""" Return https://api.hostname.tld """
return "https://api." + self.get_hostname()

# https://oauth.sandbox.trainingpeaks.com/oauth/deauthorize


scope_delimiter = " "

def complete_login(self, request, app, token, **kwargs):
headers = {"Authorization": "Bearer {0}".format(token.token)}
response = requests.get(self.profile_url, headers=headers)
response.raise_for_status()
extra_data = response.json()
return self.get_provider().sociallogin_from_response(request, extra_data)


oauth2_login = OAuth2LoginView.adapter_view(TrainingPeaksOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(TrainingPeaksOAuth2Adapter)
1 change: 1 addition & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ settings.py (Important - Please note 'django.contrib.sites' is required as INSTA
'allauth.socialaccount.providers.strava',
'allauth.socialaccount.providers.stripe',
'allauth.socialaccount.providers.telegram',
'allauth.socialaccount.providers.trainingpeaks',
'allauth.socialaccount.providers.trello',
'allauth.socialaccount.providers.tumblr',
'allauth.socialaccount.providers.twentythreeandme',
Expand Down
2 changes: 2 additions & 0 deletions docs/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ Supported Providers

- Telegram

- TrainingPeaks (OAuth2)

- Trello (OAuth)

- Tumblr (OAuth)
Expand Down
26 changes: 26 additions & 0 deletions docs/providers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,32 @@ See more in documentation
https://stripe.com/docs/connect/standalone-accounts


TrainingPeaks
-------------

You need to request an API Partnership to get your OAth credentials:

https://api.trainingpeaks.com/request-access

Make sure to request scope `athlete:profile` to be able to use OAuth
for user login (default if setting `SCOPE` is omitted).

In development you should only use the sandbox services, which is the
default unless you set `USE_PRODUCTION` to `True`.

.. code-block:: python
SOCIALACCOUNT_PROVIDERS = {
'trainingpeaks': {
'SCOPE': ['athlete:profile'],
'USE_PRODUCTION': False,
}
}
API documentation:

https://github.com/TrainingPeaks/PartnersAPI/wiki

Trello
------

Expand Down
1 change: 1 addition & 0 deletions test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
"allauth.socialaccount.providers.strava",
"allauth.socialaccount.providers.stripe",
"allauth.socialaccount.providers.telegram",
"allauth.socialaccount.providers.trainingpeaks",
"allauth.socialaccount.providers.trello",
"allauth.socialaccount.providers.tumblr",
"allauth.socialaccount.providers.twentythreeandme",
Expand Down

0 comments on commit c50a30a

Please sign in to comment.