Skip to content

Commit

Permalink
Merge d3bea1c into b4a29cc
Browse files Browse the repository at this point in the history
  • Loading branch information
peterwade153 committed Jun 21, 2018
2 parents b4a29cc + d3bea1c commit 97ad7d8
Show file tree
Hide file tree
Showing 15 changed files with 453 additions and 8 deletions.
8 changes: 8 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
# Your twitter handle, if you have one for this instance.
#WGER_SETTINGS['TWITTER'] = ''

#Fitbit settings
REDIRECT_URI = os.getenv('REDIRECT_URI', None)
CLIENT_ID = os.getenv('CLIENT_ID',None)
CLIENT_SECRET = os.getenv('CLIENT_SECRET', None)
SCOPE = ('weight','activity') # user information wger will have access to
Authorization_URI = os.getenv('Authorization_URI', None)
ACCESS_TOKEN = os.getenv('ACCESS_TOKEN', None)

try:
from local_settings import *
except ImportError:
Expand Down
4 changes: 3 additions & 1 deletion wger/core/templates/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<li><a href="{% url 'exercise:exercise:overview' %}">{% trans "Exercises" %}</a></li>
<li><a href="{% url 'exercise:muscle:overview' %}">{% trans "Muscle overview" %}</a></li>
<li><a href="{% url 'exercise:equipment:overview' %}">{% trans "Equipment overview" %}</a></li>
<li><a href="{% url 'exercise:exercise:fitbit_overview' %}">{% trans "FitBit overview" %}</a></li>
{% if user.is_authenticated and not user.userprofile.is_temporary and not trainer_identity %}
<li class="divider"></li>
<li><a href="{% url 'exercise:exercise:add' %}">{% trans "Add new exercise" %}</a><li>
Expand Down Expand Up @@ -84,6 +85,7 @@
rel="nofollow">{% trans "Weight" %} <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{% url 'weight:overview' user.username %}" rel="nofollow">{% trans "Weight overview" %}</a></li>
<li><a href="{% url 'weight:fitbit_overview' user.username %}" rel="nofollow">{% trans "Fitbit overview" %}</a></li>
<li><a href="{% url 'weight:add' %}" rel="nofollow">{% trans "Add weight entry" %}</a></li>
</ul>
</li>
Expand Down Expand Up @@ -199,7 +201,7 @@
<ul class="dropdown-menu">
<li>
<a href="{% url 'core:user:preferences' %}">{% trans "My preferences" %}</a>
</li>
</li>
{% if not trainer_identity %}
<li class="divider"></li>
<li>
Expand Down
3 changes: 3 additions & 0 deletions wger/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@
url(r'^list',
user.UserListView.as_view(),
name='list'),
url(r'^fitbit$',
user.fitbit,
name='fitbit'),

# Password reset is implemented by Django, no need to cook our own soup here
# (besides the templates)
Expand Down
123 changes: 123 additions & 0 deletions wger/core/views/fitbit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import os
import urllib
import base64
import requests

class Fitbit():
'''
Enables users get their weight and exercise information from fitbit
'''

SCOPE = ('weight','activity')

def get_authorization_uri(self):
# Get a unique URI for the user

params = {
'client_id' : os.getenv('CLIENT_ID'),
'response_type' : 'code',
'redirect_uri' : os.getenv('REDIRECT_URI'),
'scope' : ' '.join(self.SCOPE)
}

#encode the params
encoded_params = urllib.parse.urlencode(params)
return os.getenv('Authorization_URI') +'?'+ encoded_params

def get_access_token(self, code):
# gets access_token if access_code passes the code_challenge

#authentication header
data_str = os.getenv('ACCESS_TOKEN')+':'+os.getenv('CLIENT_SECRET')
auth_header = base64.b64encode(data_str.encode('utf-8'))
header = {
'Authorization' : os.getenv('Authorization'),
'Content-Type' : 'application/x-www-form-urlencoded'
}
params = {
'code' : code,
'grant_type' : 'authorization_code',
'client_id' : os.getenv('CLIENT_ID'),
'redirect_uri' : os.getenv('REDIRECT_URI'),
}
#request
response = requests.post(os.getenv('ACCESS_TOKEN'), data=params, headers=header)
resp = response.json()

if response.status_code != 200:
raise Exception("something went wrong (%s):%s" %(resp['errors'][0]['errorType'], resp['errors'][0]['message']))

#get our token
token = dict()
token['access_token'] = resp['access_token']
token['refresh_token'] = resp['refresh_token']

return token

def refresh_access_token(self, token):
#refreshes token after it has expired

#authentication header
auth_header = base64.b64encode(os.getenv('ACCESS_TOKEN')+':'+os.getenv('CLIENT_SECRET'))
header = {
'Authorization' : os.getenv('Authorization'),
'Content-Type' : 'application/x-www-form-urlencoded'
}
params = {
'grant_type' : 'refresh_token',
'refresh_token' : token['refresh_token']
}

#request
response = requests.post(os.getenv('ACCESS_TOKEN'), data=params, headers=header)
response = response.json()
status_code = response.status_code

if status_code != 200:
raise Exception("something went wrong")

#update our token
token['access_token'] = response['access_token']
token['refresh_token'] = response['refresh_token']

return token

def get_weight_info(self, token):
# get user weight info from fitbit

header = {
'Authorization' : 'Bearer %s' % token['access_token']
}
url = os.getenv('WEIGHT_URL')
response = requests.get(url, headers=header)

status_code = response.status_code
if status_code == 200:
return response.json()
elif status_code == 401:
# Invalid token, refresh token and fetch weight again
token = self.refresh_access_token(token)
self.get_weight_info(token)
else:
raise Exception("Action Failed")

def get_exercise_info(self, token):
# get user exercise info

header = {
'Authorization' : 'Bearer %s' % token['access_token']
}
url = os.getenv('EXERCISE_URL')
response = requests.get(url, headers=header)
status_code = response.status_code

if status_code == 200:
return response.json()
elif status_code == 401:
# Invalid token, refresh token and fetch weight again
token = self.refresh_access_token(token)
self.get_exercise_info(token)
else:
raise Exception("Action Failed")


3 changes: 2 additions & 1 deletion wger/core/views/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from wger.nutrition.models import NutritionPlan
from wger.weight.models import WeightEntry
from wger.weight.helpers import get_last_entries
from wger.weight import helpers


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -109,7 +110,7 @@ def dashboard(request):
except ObjectDoesNotExist:
weight = False
template_data['weight'] = weight
template_data['last_weight_entries'] = get_last_entries(request.user)
template_data['last_weight_entries'] = helpers.get_last_entries(request.user)

# Format a bit the days so it doesn't have to be done in the template
used_days = {}
Expand Down
73 changes: 72 additions & 1 deletion wger/core/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
# You should have received a copy of the GNU Affero General Public License

import logging
import datetime

from django.shortcuts import render, get_object_or_404
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.template.context_processors import csrf
from django.core.urlresolvers import reverse
Expand Down Expand Up @@ -65,7 +66,11 @@
Contract,
Gym
)
<<<<<<< HEAD
from wger.core.models import UserProfile
=======
from .fitbit import Fitbit
>>>>>>> feat(Add-fitbit): Add Fitbit integration

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -432,6 +437,72 @@ def preferences(request):
return render(request, 'user/preferences.html', template_data)


@login_required
def fitbit(request):
'''
Allow user get data from fitbit
'''
fitbit = Fitbit()
login_url = fitbit.get_authorization_uri()
return redirect(login_url)

@login_required
def fetch_weight_data(request, code):
'''
fetch weight data from fitbit
'''
fitbit = Fitbit()
fitbit_weight_list = []
# get token if passed code passes code challenge
token = fitbit.get_access_token(code)

# storing the token in the session
request.session['token'] = token

try:
data = fitbit.get_weight_info(token)
if data:
for log in data['body-weight']:
old_format = log['dateTime']
year, month, day = old_format.split('-')
old_date = datetime.datetime(int(year),int(month),int(day))
date1 = datetime.datetime.strptime(old_format, "%Y-%m-%d")
new_date = datetime.datetime.strftime(date1,"%B %d, %Y")

fitbit_weight = {
'weight' : log['value'],
'date' : new_date
}
fitbit_weight_list.append(fitbit_weight)
return fitbit_weight_list

except Exception as e:
return e

@login_required
def fetch_exercise_data(request, token):
'''
fetch exercise data from fitbit
'''
fitbit = Fitbit()
fitbit_exercises = []

data = fitbit.get_exercise_info(token)
if data:
for log in data['activities']:
# change duration from milliseconds back to hours
millisec_format = log['duration']
hr_format = round(float(millisec_format)*(1/3600000), 2)
fitbit_exercise = {
'name' : log['activityParentName'],
'description' : log['description'],
'duration' : hr_format
}
fitbit_exercises.append(fitbit_exercise)

return fitbit_exercises


class UserDeactivateView(LoginRequiredMixin,
WgerMultiplePermissionRequiredMixin,
RedirectView):
Expand Down
59 changes: 59 additions & 0 deletions wger/exercises/templates/exercise/fitbit_overview.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{% extends "base.html" %}
{% load i18n staticfiles wger_extras %}

{# #}
{# Title #}
{# #}
{% block title %}{% trans "FitBit Exercise overview" %}{% endblock %}

{# #}
{# Header #}
{# #}
{% block header %}

{% endblock %}



{# #}
{# Content #}
{# #}
{% block content %}
<div id="current-username" data-current-username="{{ owner_user.username }}"></div>


{% if not fitbit_exercise_data %}
<p>
{% trans "There are no Fitbit exercises entries." %}
</p>
{% endif %}


{% if is_owner %}
{% if fitbit_exercise_data %}
<table class="table">
<tr>
<th>{% trans 'Exercise' %}</th>
<th>{% trans 'Description' %}</th>
<th>{% trans 'Duration (Hrs)' %}</th>
</tr>
{% for entry_detail in fitbit_exercise_data %}
<tr>
<td>{{ entry_detail.name }}</td>
<td>{{ entry_detail.description }}</td>
<td>{{ entry_detail.duration }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endif %}

{% endblock %}


{# #}
{# Side bar #}
{# #}
{% block sidebar %}
{% endblock %}

1 change: 1 addition & 0 deletions wger/exercises/templates/exercise/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

{% cache cache_timeout exercise-overview language.id %}
{% regroup exercises by category as exercise_list %}

<ul class="nav nav-tabs">
{% for item in exercise_list %}
<li {% if forloop.first %}class="active"{% endif %}>
Expand Down
3 changes: 3 additions & 0 deletions wger/exercises/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@
url(r'^overview/$',
exercises.ExerciseListView.as_view(),
name='overview'),
url(r'^fitbit_overview/$',
exercises.fitbit_overview,
name='fitbit_overview'),
url(r'^(?P<id>\d+)/view/$',
exercises.view,
name='view'),
Expand Down
17 changes: 17 additions & 0 deletions wger/exercises/views/exercises.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
)
from wger.config.models import LanguageConfig
from wger.weight.helpers import process_log_entries
from wger.core.views.user import fetch_exercise_data
from wger.utils.helpers import check_access


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -149,6 +151,21 @@ def view(request, id, slug=None):

return render(request, 'exercise/view.html', template_data)

def fitbit_overview(request):
'''
Shows exercise data
'''
is_owner = check_access(request.user)

template_data = {}
data_list = []

token = request.session['token']
data = fetch_exercise_data(request, token)

template_data['is_owner'] = is_owner
template_data['fitbit_exercise_data'] = data
return render(request, 'exercise/fitbit_overview.html', template_data)

class ExercisesEditAddView(WgerFormMixin):
'''
Expand Down
Loading

0 comments on commit 97ad7d8

Please sign in to comment.