From 304e84f7b3328c01bbee8b20afb0570a0cf86a50 Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Wed, 10 Jan 2018 15:54:59 -0600 Subject: [PATCH 01/31] Add PyCharm .idea/ folder to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a14f5e8..e4fa4c7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ docs/_build/ .coverage htmlcov/ *~ +.idea \ No newline at end of file From f09cc98249b77b523b2333f6279bec081813b283 Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Wed, 10 Jan 2018 16:08:09 -0600 Subject: [PATCH 02/31] Modify models and migrations to work with Django 2. The `on_delete` kwarg was added and the import syntax of `reverse` was changed in all found locations. --- fitapp/migrations/0001_initial.py | 9 ++- fitapp/migrations/0006_help_text.py | 12 +++- fitapp/migrations/0009_auto_20180110_1605.py | 70 ++++++++++++++++++++ fitapp/models.py | 16 ++++- fitapp/tests/base.py | 2 +- fitapp/tests/test_integration.py | 2 +- fitapp/tests/test_retrieval.py | 2 +- fitapp/views.py | 2 +- requirements/dev.txt | 2 +- 9 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 fitapp/migrations/0009_auto_20180110_1605.py diff --git a/fitapp/migrations/0001_initial.py b/fitapp/migrations/0001_initial.py index 926b26d..7699194 100644 --- a/fitapp/migrations/0001_initial.py +++ b/fitapp/migrations/0001_initial.py @@ -42,7 +42,8 @@ class Migration(migrations.Migration): ('fitbit_user', models.CharField(unique=True, max_length=32)), ('auth_token', models.TextField()), ('auth_secret', models.TextField()), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), + ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE)), ], options={ }, @@ -55,13 +56,15 @@ class Migration(migrations.Migration): migrations.AddField( model_name='timeseriesdata', name='resource_type', - field=models.ForeignKey(to='fitapp.TimeSeriesDataType'), + field=models.ForeignKey(to='fitapp.TimeSeriesDataType', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='timeseriesdata', name='user', - field=models.ForeignKey(to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( diff --git a/fitapp/migrations/0006_help_text.py b/fitapp/migrations/0006_help_text.py index 630106c..51d0454 100644 --- a/fitapp/migrations/0006_help_text.py +++ b/fitapp/migrations/0006_help_text.py @@ -20,12 +20,17 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='timeseriesdata', name='resource_type', - field=models.ForeignKey(help_text=b'The type of time series data', to='fitapp.TimeSeriesDataType'), + field=models.ForeignKey(help_text=b'The type of time series data', + to='fitapp.TimeSeriesDataType', + on_delete=models.CASCADE), ), migrations.AlterField( model_name='timeseriesdata', name='user', - field=models.ForeignKey(help_text=b"The data's user", to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(help_text=b"The data's user", + to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE + ), ), migrations.AlterField( model_name='timeseriesdata', @@ -65,6 +70,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='userfitbit', name='user', - field=models.OneToOneField(to=settings.AUTH_USER_MODEL, help_text=b'The user'), + field=models.OneToOneField(to=settings.AUTH_USER_MODEL, help_text=b'The user', + on_delete=models.CASCADE), ), ] diff --git a/fitapp/migrations/0009_auto_20180110_1605.py b/fitapp/migrations/0009_auto_20180110_1605.py new file mode 100644 index 0000000..06b9a48 --- /dev/null +++ b/fitapp/migrations/0009_auto_20180110_1605.py @@ -0,0 +1,70 @@ +# Generated by Django 2.0.1 on 2018-01-10 16:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('fitapp', '0008_remove_userfitbit_auth_secret'), + ] + + operations = [ + migrations.AlterField( + model_name='timeseriesdata', + name='date', + field=models.DateField(help_text='The date the data was recorded'), + ), + migrations.AlterField( + model_name='timeseriesdata', + name='resource_type', + field=models.ForeignKey(help_text='The type of time series data', on_delete=django.db.models.deletion.CASCADE, to='fitapp.TimeSeriesDataType'), + ), + migrations.AlterField( + model_name='timeseriesdata', + name='user', + field=models.ForeignKey(help_text="The data's user", on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='timeseriesdata', + name='value', + field=models.CharField(default=None, help_text='The value of the data. This is typically a number, though saved as a string here. The units can be inferred from the data type. For example, for step data the value might be "9783" (the units) would be "steps"', max_length=32, null=True), + ), + migrations.AlterField( + model_name='timeseriesdatatype', + name='category', + field=models.IntegerField(choices=[(0, 'foods'), (1, 'activities'), (2, 'sleep'), (3, 'body')], help_text='The category of the time series data, one of: 0(foods), 1(activities), 2(sleep), 3(body)'), + ), + migrations.AlterField( + model_name='timeseriesdatatype', + name='resource', + field=models.CharField(help_text='The specific time series resource. This is the string that will be used for the [resource-path] of the API url referred to in the Fitbit documentation', max_length=128), + ), + migrations.AlterField( + model_name='userfitbit', + name='access_token', + field=models.TextField(help_text='The OAuth2 access token'), + ), + migrations.AlterField( + model_name='userfitbit', + name='expires_at', + field=models.FloatField(help_text='The timestamp when the access token expires'), + ), + migrations.AlterField( + model_name='userfitbit', + name='fitbit_user', + field=models.CharField(help_text='The fitbit user ID', max_length=32, unique=True), + ), + migrations.AlterField( + model_name='userfitbit', + name='refresh_token', + field=models.TextField(help_text='The OAuth2 refresh token'), + ), + migrations.AlterField( + model_name='userfitbit', + name='user', + field=models.OneToOneField(help_text='The user', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/fitapp/models.py b/fitapp/models.py index ad81bc7..6a6a040 100644 --- a/fitapp/models.py +++ b/fitapp/models.py @@ -10,7 +10,10 @@ class UserFitbit(models.Model): """ A user's fitbit credentials, allowing API access """ user = models.OneToOneField( - UserModel, help_text='The user') + UserModel, + help_text='The user', + on_delete=models.CASCADE + ) fitbit_user = models.CharField( max_length=32, unique=True, help_text='The fitbit user ID') access_token = models.TextField(help_text='The OAuth2 access token') @@ -92,9 +95,16 @@ class TimeSeriesData(models.Model): https://dev.fitbit.com/docs/body/#body-time-series """ - user = models.ForeignKey(UserModel, help_text="The data's user") + user = models.ForeignKey( + UserModel, + help_text="The data's user", + on_delete=models.CASCADE + ) resource_type = models.ForeignKey( - TimeSeriesDataType, help_text='The type of time series data') + TimeSeriesDataType, + help_text='The type of time series data', + on_delete=models.CASCADE + ) date = models.DateField(help_text='The date the data was recorded') value = models.CharField( null=True, diff --git a/fitapp/tests/base.py b/fitapp/tests/base.py index 204f9d2..a7c1909 100644 --- a/fitapp/tests/base.py +++ b/fitapp/tests/base.py @@ -11,7 +11,7 @@ from string import letters as ascii_letters from django.contrib.auth.models import User -from django.core.urlresolvers import reverse +from django.urls import reverse from django.test import TestCase from fitbit.api import Fitbit diff --git a/fitapp/tests/test_integration.py b/fitapp/tests/test_integration.py index 42fc04e..941c1aa 100644 --- a/fitapp/tests/test_integration.py +++ b/fitapp/tests/test_integration.py @@ -8,7 +8,7 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.models import AnonymousUser -from django.core.urlresolvers import reverse +from django.urls import reverse from django.http import HttpRequest from django.test.utils import override_settings from fitbit.exceptions import HTTPConflict diff --git a/fitapp/tests/test_retrieval.py b/fitapp/tests/test_retrieval.py index 0b77856..f0f12ff 100644 --- a/fitapp/tests/test_retrieval.py +++ b/fitapp/tests/test_retrieval.py @@ -9,7 +9,7 @@ from dateutil import parser from django.core.cache import cache from django.core.files.uploadedfile import InMemoryUploadedFile -from django.core.urlresolvers import reverse +from django.urls import reverse from django.test.utils import override_settings from freezegun import freeze_time from mock import MagicMock, patch diff --git a/fitapp/views.py b/fitapp/views.py index 4915a8b..bddce06 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.signals import user_logged_in from django.core.exceptions import ImproperlyConfigured -from django.core.urlresolvers import reverse +from django.urls import reverse from django.dispatch import receiver from django.http import HttpResponse, HttpResponseServerError, Http404 from django.shortcuts import redirect, render diff --git a/requirements/dev.txt b/requirements/dev.txt index 274bd34..8555c3e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -2,5 +2,5 @@ -r test.txt django-debug-toolbar -django>=1.8,<1.11 +Django==2.0.1 tox>=1.8,<2.4 From c84182595461a47570c580eb1b3d8fb0dac1765d Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Wed, 10 Jan 2018 16:11:54 -0600 Subject: [PATCH 03/31] Change user.is_authenticated() to user.is_authenticated. --- fitapp/utils.py | 2 +- fitapp/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fitapp/utils.py b/fitapp/utils.py index 80c79e3..1cb8ca8 100644 --- a/fitapp/utils.py +++ b/fitapp/utils.py @@ -35,7 +35,7 @@ def is_integrated(user): :param user: A Django User. """ - if user.is_authenticated() and user.is_active: + if user.is_authenticated and user.is_active: return UserFitbit.objects.filter(user=user).exists() return False diff --git a/fitapp/views.py b/fitapp/views.py index bddce06..f61a5b4 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -147,7 +147,7 @@ def complete(request): def create_fitbit_session(sender, request, user, **kwargs): """ If the user is a fitbit user, update the profile in the session. """ - if user.is_authenticated() and utils.is_integrated(user) and \ + if user.is_authenticated and utils.is_integrated(user) and \ user.is_active: fbuser = UserFitbit.objects.filter(user=user) if fbuser.exists(): @@ -421,7 +421,7 @@ def get_data(request, category, resource): return make_response(104) fitapp_subscribe = utils.get_setting('FITAPP_SUBSCRIBE') - if not user.is_authenticated() or not user.is_active: + if not user.is_authenticated or not user.is_active: return make_response(101) if not fitapp_subscribe and not utils.is_integrated(user): return make_response(102) From c451a65a327c14252e9383a769da6121f353a6cc Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Wed, 10 Jan 2018 16:16:50 -0600 Subject: [PATCH 04/31] Change to . All tests passing. --- django_fitapp | 0 fitapp/tests/test_verification.py | 2 +- test_settings.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 django_fitapp diff --git a/django_fitapp b/django_fitapp new file mode 100644 index 0000000..e69de29 diff --git a/fitapp/tests/test_verification.py b/fitapp/tests/test_verification.py index 930a5f7..3749f66 100644 --- a/fitapp/tests/test_verification.py +++ b/fitapp/tests/test_verification.py @@ -3,7 +3,7 @@ https://dev.fitbit.com/docs/subscriptions/#verify-a-subscriber """ -from django.core.urlresolvers import reverse +from django.urls import reverse from django.test import TestCase from django.test.utils import override_settings diff --git a/test_settings.py b/test_settings.py index f623053..57b9ca9 100644 --- a/test_settings.py +++ b/test_settings.py @@ -44,7 +44,7 @@ }, ] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', ) From 26549c0159a618f9e76b29a671a4f9168ce6311f Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Wed, 10 Jan 2018 16:25:28 -0600 Subject: [PATCH 05/31] Update dependencies. Also adds two new dev dependencies for convenience. --- requirements/base.txt | 6 +++--- requirements/dev.txt | 7 +++++-- requirements/test.txt | 10 +++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index f402638..3122859 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ fitbit>=0.3.0 -celery>=3.1.13 -simplejson -six +celery>=4.1,<4.2 +simplejson>=3.13,<4 +six \ No newline at end of file diff --git a/requirements/dev.txt b/requirements/dev.txt index 8555c3e..306bcf7 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -2,5 +2,8 @@ -r test.txt django-debug-toolbar -Django==2.0.1 -tox>=1.8,<2.4 +Django>=2.0.1,<2.1 +tox>=2.9,<3 + +pip-review +pipdeptree \ No newline at end of file diff --git a/requirements/test.txt b/requirements/test.txt index a5f6ec8..142a0c8 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,5 +1,5 @@ -coverage>4 -mock>=1,<1.4 -freezegun>=0.2.3,<0.4 -requests-mock>=1.2.0 -Sphinx>=1.2 +coverage +mock>=2,<3 +freezegun>=0.3.9,<0.4 +requests-mock>=1.4.0,<1.5 +Sphinx>=1.6 From 5ae36ccfc551bc940824a69ac8982ab4335973c4 Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Wed, 10 Jan 2018 16:26:07 -0600 Subject: [PATCH 06/31] Delete unneeded django_fitapp file. --- django_fitapp | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 django_fitapp diff --git a/django_fitapp b/django_fitapp deleted file mode 100644 index e69de29..0000000 From b58789973459a2d202098f576073a05bf9384dce Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Wed, 10 Jan 2018 16:28:41 -0600 Subject: [PATCH 07/31] Update changelog and setup.py. --- CHANGELOG.rst | 6 ++++++ setup.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3d6c0c7..f28e06a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,9 @@ +0.4.0 (2018-01-10) +------------------ + +- Adds Django 2.0.1 support +- Updates dependencies + 0.3.0 (2017-01-25) ------------------ diff --git a/setup.py b/setup.py index e087580..3276e5a 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ description="Django integration for python-fitbit", long_description=open("README.md").read(), classifiers=[ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Alpha", 'License :: OSI Approved :: Apache Software License', "Environment :: Web Environment", "Framework :: Django", @@ -28,6 +28,7 @@ "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation", "Programming Language :: Python :: Implementation :: PyPy" ], From d6a90c6950335eaaf516199dc7b7c83e32dfcdae Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Wed, 10 Jan 2018 16:36:09 -0600 Subject: [PATCH 08/31] Reformat project code to conform to PEP8. --- fitapp/decorators.py | 3 ++ fitapp/forms.py | 1 - fitapp/management/commands/refresh_tokens.py | 2 +- fitapp/migrations/0001_initial.py | 19 +++++++---- fitapp/migrations/0002_initial_data.py | 7 ++-- .../0003_add_refresh_token_field.py | 1 - .../0004_rename_auth_token_to_access_token.py | 3 +- .../0005_upgrade_oauth1_tokens_to_oauth2.py | 7 ++-- fitapp/migrations/0006_help_text.py | 17 ++++++---- .../migrations/0007_userfitbit_expires_at.py | 4 +-- .../0008_remove_userfitbit_auth_secret.py | 3 +- fitapp/migrations/0009_auto_20180110_1605.py | 33 +++++++++++++------ fitapp/models.py | 1 - fitapp/tasks.py | 5 ++- fitapp/templates/fitapp/error.html | 16 ++++----- fitapp/templatetags/fitbit.py | 3 +- fitapp/tests/__init__.py | 3 +- fitapp/tests/base.py | 6 ++-- fitapp/tests/celery.py | 1 - fitapp/tests/test_commands.py | 6 +--- fitapp/tests/test_integration.py | 12 +++---- fitapp/tests/test_models.py | 2 +- fitapp/tests/test_retrieval.py | 19 ++++++----- fitapp/tests/test_verification.py | 2 +- fitapp/urls.py | 1 - fitapp/utils.py | 3 +- fitapp/views.py | 19 +++++------ 27 files changed, 105 insertions(+), 94 deletions(-) diff --git a/fitapp/decorators.py b/fitapp/decorators.py index 0aa981c..23e93fb 100644 --- a/fitapp/decorators.py +++ b/fitapp/decorators.py @@ -42,6 +42,7 @@ def my_view(request): """ if not msg: msg = utils.get_setting('FITAPP_DECORATOR_MESSAGE') + def inner_decorator(view_func): def wrapped(request, *args, **kwargs): user = request.user @@ -49,5 +50,7 @@ def wrapped(request, *args, **kwargs): text = msg(request) if callable(msg) else msg messages.error(request, text) return view_func(request, *args, **kwargs) + return wraps(view_func)(wrapped) + return inner_decorator diff --git a/fitapp/forms.py b/fitapp/forms.py index 04e36ba..19fb752 100644 --- a/fitapp/forms.py +++ b/fitapp/forms.py @@ -2,7 +2,6 @@ from . import utils - INPUT_FORMATS = ['%Y-%m-%d'] diff --git a/fitapp/management/commands/refresh_tokens.py b/fitapp/management/commands/refresh_tokens.py index ef72bba..178ccd9 100644 --- a/fitapp/management/commands/refresh_tokens.py +++ b/fitapp/management/commands/refresh_tokens.py @@ -13,7 +13,7 @@ import time -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from oauthlib.oauth2.rfc6749.errors import InvalidGrantError from fitapp.models import UserFitbit diff --git a/fitapp/migrations/0001_initial.py b/fitapp/migrations/0001_initial.py index 7699194..aaa069a 100644 --- a/fitapp/migrations/0001_initial.py +++ b/fitapp/migrations/0001_initial.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -15,7 +14,9 @@ class Migration(migrations.Migration): migrations.CreateModel( name='TimeSeriesData', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), ('date', models.DateField()), ('value', models.CharField(default=None, max_length=32, null=True)), ], @@ -26,8 +27,12 @@ class Migration(migrations.Migration): migrations.CreateModel( name='TimeSeriesDataType', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('category', models.IntegerField(choices=[(0, b'foods'), (1, b'activities'), (2, b'sleep'), (3, b'body')])), + ('id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), + ('category', models.IntegerField( + choices=[(0, b'foods'), (1, b'activities'), (2, b'sleep'), + (3, b'body')])), ('resource', models.CharField(max_length=128)), ], options={ @@ -38,7 +43,9 @@ class Migration(migrations.Migration): migrations.CreateModel( name='UserFitbit', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, + primary_key=True)), ('fitbit_user', models.CharField(unique=True, max_length=32)), ('auth_token', models.TextField()), ('auth_secret', models.TextField()), diff --git a/fitapp/migrations/0002_initial_data.py b/fitapp/migrations/0002_initial_data.py index 8d3b913..d6fcb6b 100644 --- a/fitapp/migrations/0002_initial_data.py +++ b/fitapp/migrations/0002_initial_data.py @@ -2,13 +2,14 @@ from __future__ import unicode_literals import os -from sys import path + from django.core import serializers -from django.db import models, migrations +from django.db import migrations fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) fixture_filename = 'initial_data.json' + def load_fixture(apps, schema_editor): fixture_file = os.path.join(fixture_dir, fixture_filename) @@ -18,6 +19,7 @@ def load_fixture(apps, schema_editor): obj.save() fixture.close() + def unload_fixture(apps, schema_editor): "Brutally deleting all entries for this model..." @@ -26,7 +28,6 @@ def unload_fixture(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ('fitapp', '0001_initial'), ] diff --git a/fitapp/migrations/0003_add_refresh_token_field.py b/fitapp/migrations/0003_add_refresh_token_field.py index 6419fff..e5c5174 100644 --- a/fitapp/migrations/0003_add_refresh_token_field.py +++ b/fitapp/migrations/0003_add_refresh_token_field.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('fitapp', '0002_initial_data'), ] diff --git a/fitapp/migrations/0004_rename_auth_token_to_access_token.py b/fitapp/migrations/0004_rename_auth_token_to_access_token.py index 4561cc4..05633b0 100644 --- a/fitapp/migrations/0004_rename_auth_token_to_access_token.py +++ b/fitapp/migrations/0004_rename_auth_token_to_access_token.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): - dependencies = [ ('fitapp', '0003_add_refresh_token_field'), ] diff --git a/fitapp/migrations/0005_upgrade_oauth1_tokens_to_oauth2.py b/fitapp/migrations/0005_upgrade_oauth1_tokens_to_oauth2.py index f12ef78..7168e11 100644 --- a/fitapp/migrations/0005_upgrade_oauth1_tokens_to_oauth2.py +++ b/fitapp/migrations/0005_upgrade_oauth1_tokens_to_oauth2.py @@ -1,9 +1,9 @@ -from django.db import migrations, models - +from django.db import migrations from fitbit.api import FitbitOauth2Client -from fitapp.utils import get_setting from oauthlib.oauth2.rfc6749.errors import MissingTokenError +from fitapp.utils import get_setting + def forwards(apps, schema_editor): UserFitbit = apps.get_model('fitapp', 'UserFitbit') @@ -29,7 +29,6 @@ def backwards(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ('fitapp', '0004_rename_auth_token_to_access_token'), ] diff --git a/fitapp/migrations/0006_help_text.py b/fitapp/migrations/0006_help_text.py index 51d0454..a424b7f 100644 --- a/fitapp/migrations/0006_help_text.py +++ b/fitapp/migrations/0006_help_text.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('fitapp', '0005_upgrade_oauth1_tokens_to_oauth2'), ] @@ -35,17 +34,22 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='timeseriesdata', name='value', - field=models.CharField(default=None, max_length=32, null=True, help_text=b'The value of the data. This is typically a number, though saved as a string here. The units can be inferred from the data type. For example, for step data the value might be "9783" (the units) would be "steps"'), + field=models.CharField(default=None, max_length=32, null=True, + help_text=b'The value of the data. This is typically a number, though saved as a string here. The units can be inferred from the data type. For example, for step data the value might be "9783" (the units) would be "steps"'), ), migrations.AlterField( model_name='timeseriesdatatype', name='category', - field=models.IntegerField(help_text=b'The category of the time series data, one of: 0(foods), 1(activities), 2(sleep), 3(body)', choices=[(0, b'foods'), (1, b'activities'), (2, b'sleep'), (3, b'body')]), + field=models.IntegerField( + help_text=b'The category of the time series data, one of: 0(foods), 1(activities), 2(sleep), 3(body)', + choices=[(0, b'foods'), (1, b'activities'), (2, b'sleep'), (3, b'body')]), ), migrations.AlterField( model_name='timeseriesdatatype', name='resource', - field=models.CharField(help_text=b'The specific time series resource. This is the string that will be used for the [resource-path] of the API url referred to in the Fitbit documentation', max_length=128), + field=models.CharField( + help_text=b'The specific time series resource. This is the string that will be used for the [resource-path] of the API url referred to in the Fitbit documentation', + max_length=128), ), migrations.AlterField( model_name='userfitbit', @@ -60,7 +64,8 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='userfitbit', name='fitbit_user', - field=models.CharField(help_text=b'The fitbit user ID', unique=True, max_length=32), + field=models.CharField(help_text=b'The fitbit user ID', unique=True, + max_length=32), ), migrations.AlterField( model_name='userfitbit', diff --git a/fitapp/migrations/0007_userfitbit_expires_at.py b/fitapp/migrations/0007_userfitbit_expires_at.py index 5f8f5e8..a59c01c 100644 --- a/fitapp/migrations/0007_userfitbit_expires_at.py +++ b/fitapp/migrations/0007_userfitbit_expires_at.py @@ -8,7 +8,6 @@ class Migration(migrations.Migration): - dependencies = [ ('fitapp', '0006_help_text'), ] @@ -17,7 +16,8 @@ class Migration(migrations.Migration): migrations.AddField( model_name='userfitbit', name='expires_at', - field=models.FloatField(default=time.time(), help_text=b'The timestamp when the access token expires'), + field=models.FloatField(default=time.time(), + help_text=b'The timestamp when the access token expires'), preserve_default=False, ), ] diff --git a/fitapp/migrations/0008_remove_userfitbit_auth_secret.py b/fitapp/migrations/0008_remove_userfitbit_auth_secret.py index 7ea5bdd..bd3f9b4 100644 --- a/fitapp/migrations/0008_remove_userfitbit_auth_secret.py +++ b/fitapp/migrations/0008_remove_userfitbit_auth_secret.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): - dependencies = [ ('fitapp', '0007_userfitbit_expires_at'), ] diff --git a/fitapp/migrations/0009_auto_20180110_1605.py b/fitapp/migrations/0009_auto_20180110_1605.py index 06b9a48..869c99a 100644 --- a/fitapp/migrations/0009_auto_20180110_1605.py +++ b/fitapp/migrations/0009_auto_20180110_1605.py @@ -1,12 +1,11 @@ # Generated by Django 2.0.1 on 2018-01-10 16:05 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ ('fitapp', '0008_remove_userfitbit_auth_secret'), ] @@ -20,27 +19,37 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='timeseriesdata', name='resource_type', - field=models.ForeignKey(help_text='The type of time series data', on_delete=django.db.models.deletion.CASCADE, to='fitapp.TimeSeriesDataType'), + field=models.ForeignKey(help_text='The type of time series data', + on_delete=django.db.models.deletion.CASCADE, + to='fitapp.TimeSeriesDataType'), ), migrations.AlterField( model_name='timeseriesdata', name='user', - field=models.ForeignKey(help_text="The data's user", on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(help_text="The data's user", + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL), ), migrations.AlterField( model_name='timeseriesdata', name='value', - field=models.CharField(default=None, help_text='The value of the data. This is typically a number, though saved as a string here. The units can be inferred from the data type. For example, for step data the value might be "9783" (the units) would be "steps"', max_length=32, null=True), + field=models.CharField(default=None, + help_text='The value of the data. This is typically a number, though saved as a string here. The units can be inferred from the data type. For example, for step data the value might be "9783" (the units) would be "steps"', + max_length=32, null=True), ), migrations.AlterField( model_name='timeseriesdatatype', name='category', - field=models.IntegerField(choices=[(0, 'foods'), (1, 'activities'), (2, 'sleep'), (3, 'body')], help_text='The category of the time series data, one of: 0(foods), 1(activities), 2(sleep), 3(body)'), + field=models.IntegerField( + choices=[(0, 'foods'), (1, 'activities'), (2, 'sleep'), (3, 'body')], + help_text='The category of the time series data, one of: 0(foods), 1(activities), 2(sleep), 3(body)'), ), migrations.AlterField( model_name='timeseriesdatatype', name='resource', - field=models.CharField(help_text='The specific time series resource. This is the string that will be used for the [resource-path] of the API url referred to in the Fitbit documentation', max_length=128), + field=models.CharField( + help_text='The specific time series resource. This is the string that will be used for the [resource-path] of the API url referred to in the Fitbit documentation', + max_length=128), ), migrations.AlterField( model_name='userfitbit', @@ -50,12 +59,14 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='userfitbit', name='expires_at', - field=models.FloatField(help_text='The timestamp when the access token expires'), + field=models.FloatField( + help_text='The timestamp when the access token expires'), ), migrations.AlterField( model_name='userfitbit', name='fitbit_user', - field=models.CharField(help_text='The fitbit user ID', max_length=32, unique=True), + field=models.CharField(help_text='The fitbit user ID', max_length=32, + unique=True), ), migrations.AlterField( model_name='userfitbit', @@ -65,6 +76,8 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='userfitbit', name='user', - field=models.OneToOneField(help_text='The user', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + field=models.OneToOneField(help_text='The user', + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL), ), ] diff --git a/fitapp/models.py b/fitapp/models.py index 6a6a040..78a134e 100644 --- a/fitapp/models.py +++ b/fitapp/models.py @@ -2,7 +2,6 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible - UserModel = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') diff --git a/fitapp/tasks.py b/fitapp/tasks.py index 6a844d7..26ed095 100644 --- a/fitapp/tasks.py +++ b/fitapp/tasks.py @@ -9,11 +9,10 @@ from fitbit.exceptions import HTTPBadRequest, HTTPTooManyRequests from . import utils -from .models import UserFitbit, TimeSeriesData, TimeSeriesDataType - +from .models import TimeSeriesData, TimeSeriesDataType, UserFitbit logger = logging.getLogger(__name__) -LOCK_EXPIRE = 60 * 5 # Lock expires in 5 minutes +LOCK_EXPIRE = 60 * 5 # Lock expires in 5 minutes @shared_task diff --git a/fitapp/templates/fitapp/error.html b/fitapp/templates/fitapp/error.html index d97770f..47ec86f 100644 --- a/fitapp/templates/fitapp/error.html +++ b/fitapp/templates/fitapp/error.html @@ -1,11 +1,11 @@ - - Fitbit Authentication Error - - -

Fitbit Authentication Error

+ + Fitbit Authentication Error + + +

Fitbit Authentication Error

-

We encountered an error while attempting to authenticate you - through Fitbit.

- +

We encountered an error while attempting to authenticate you + through Fitbit.

+ diff --git a/fitapp/templatetags/fitbit.py b/fitapp/templatetags/fitbit.py index 5ba33be..0bae208 100644 --- a/fitapp/templatetags/fitbit.py +++ b/fitapp/templatetags/fitbit.py @@ -2,8 +2,7 @@ from fitapp import utils - -register= template.Library() +register = template.Library() @register.filter diff --git a/fitapp/tests/__init__.py b/fitapp/tests/__init__.py index 8aacae0..54ad147 100644 --- a/fitapp/tests/__init__.py +++ b/fitapp/tests/__init__.py @@ -1,8 +1,7 @@ from __future__ import absolute_import -from fitapp.tests.test_retrieval import * from fitapp.tests.test_integration import * - +from fitapp.tests.test_retrieval import * # This will make sure the app is always imported when # Django starts so that shared_task will use this app. from .celery import app diff --git a/fitapp/tests/base.py b/fitapp/tests/base.py index a7c1909..855fa99 100644 --- a/fitapp/tests/base.py +++ b/fitapp/tests/base.py @@ -1,7 +1,9 @@ -from mock import MagicMock, Mock, patch -import django import random import time + +import django +from mock import MagicMock, Mock, patch + try: from urllib.parse import urlencode from string import ascii_letters diff --git a/fitapp/tests/celery.py b/fitapp/tests/celery.py index 4e6a487..92b3782 100644 --- a/fitapp/tests/celery.py +++ b/fitapp/tests/celery.py @@ -5,7 +5,6 @@ from celery import Celery from django.conf import settings - # set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'fitapp.defaults') diff --git a/fitapp/tests/test_commands.py b/fitapp/tests/test_commands.py index 5750088..9b9eef1 100644 --- a/fitapp/tests/test_commands.py +++ b/fitapp/tests/test_commands.py @@ -1,16 +1,12 @@ import json -import requests_mock import time +import requests_mock from django.core import management from django.utils.six import StringIO from fitbit.api import FitbitOauth2Client -from mock import patch -from requests_oauthlib import OAuth2Session from fitapp.models import UserFitbit -from fitapp.management.commands import refresh_tokens - from .base import FitappTestBase diff --git a/fitapp/tests/test_integration.py b/fitapp/tests/test_integration.py index 941c1aa..f73010c 100644 --- a/fitapp/tests/test_integration.py +++ b/fitapp/tests/test_integration.py @@ -1,16 +1,15 @@ import json -import requests_mock import time - from collections import OrderedDict from datetime import datetime +import requests_mock from django.conf import settings from django.contrib import messages from django.contrib.auth.models import AnonymousUser -from django.urls import reverse from django.http import HttpRequest from django.test.utils import override_settings +from django.urls import reverse from fitbit.exceptions import HTTPConflict from freezegun import freeze_time from mock import patch @@ -18,9 +17,8 @@ from fitapp import utils from fitapp.decorators import fitbit_integration_warning -from fitapp.models import UserFitbit, TimeSeriesDataType +from fitapp.models import TimeSeriesDataType, UserFitbit from fitapp.tasks import subscribe, unsubscribe - from .base import FitappTestBase @@ -468,14 +466,14 @@ class TestSubscription(FitappTestBase): @patch('fitbit.Fitbit.subscription') def test_subscribe(self, subscription): subscribe.apply_async((self.fbuser.fitbit_user, 1,)) - subscription.assert_called_once_with(self.user.id, 1,) + subscription.assert_called_once_with(self.user.id, 1, ) @patch('fitbit.Fitbit.subscription') def test_subscribe_error(self, subscription): subscription.side_effect = HTTPConflict apply_result = subscribe.apply_async((self.fbuser.fitbit_user, 1,)) self.assertEqual(apply_result.status, 'REJECTED') - subscription.assert_called_once_with(self.user.id, 1,) + subscription.assert_called_once_with(self.user.id, 1, ) @patch('fitbit.Fitbit.subscription') @patch('fitbit.Fitbit.list_subscriptions') diff --git a/fitapp/tests/test_models.py b/fitapp/tests/test_models.py index c4f89a2..0f31380 100644 --- a/fitapp/tests/test_models.py +++ b/fitapp/tests/test_models.py @@ -1,6 +1,6 @@ -from fitapp.models import TimeSeriesDataType from django.db import IntegrityError +from fitapp.models import TimeSeriesDataType from .base import FitappTestBase diff --git a/fitapp/tests/test_retrieval.py b/fitapp/tests/test_retrieval.py index f0f12ff..0e154a8 100644 --- a/fitapp/tests/test_retrieval.py +++ b/fitapp/tests/test_retrieval.py @@ -1,25 +1,24 @@ from __future__ import absolute_import -import celery import json import sys import time - from collections import OrderedDict + +import celery from dateutil import parser from django.core.cache import cache from django.core.files.uploadedfile import InMemoryUploadedFile -from django.urls import reverse from django.test.utils import override_settings +from django.urls import reverse +from fitbit import exceptions as fitbit_exceptions +from fitbit.api import Fitbit from freezegun import freeze_time from mock import MagicMock, patch from requests_oauthlib import OAuth2Session -from fitbit import exceptions as fitbit_exceptions -from fitbit.api import Fitbit, FitbitOauth2Client - from fitapp import utils -from fitapp.models import UserFitbit, TimeSeriesData, TimeSeriesDataType +from fitapp.models import TimeSeriesData, TimeSeriesDataType, UserFitbit from fitapp.tasks import get_time_series_data try: @@ -175,7 +174,7 @@ def test_subscription_update(self, get_fitbit_data): self.assertEqual( cache.get('fitapp.get_time_series_data-lock-%s-%s-%s' % ( category, resource.resource, self.date) - ), None) + ), None) date = parser.parse(self.date) for tsd in TimeSeriesData.objects.filter(user=self.user, date=date): assert tsd.value == self.value @@ -194,7 +193,7 @@ def test_subscription_update_file(self, get_fitbit_data): self.assertEqual( cache.get('fitapp.get_time_series_data-lock-%s-%s-%s' % ( category, resource.resource, self.date) - ), None) + ), None) date = parser.parse(self.date) for tsd in TimeSeriesData.objects.filter(user=self.user, date=date): assert tsd.value == self.value @@ -289,6 +288,7 @@ def test_subscription_update_too_many(self, get_fitbit_data): self.fbuser.fitbit_user, _type, self.date) exc = fitbit_exceptions.HTTPTooManyRequests(self._error_response()) exc.retry_after_secs = 21 + def side_effect(*args, **kwargs): # Delete the cache lock after the first try and adjust the # get_fitbit_data mock to be successful @@ -299,6 +299,7 @@ def side_effect(*args, **kwargs): 'value': '34' }] raise exc + get_fitbit_data.side_effect = side_effect category = getattr(TimeSeriesDataType, self.category) resources = TimeSeriesDataType.objects.filter(category=category) diff --git a/fitapp/tests/test_verification.py b/fitapp/tests/test_verification.py index 3749f66..104ec81 100644 --- a/fitapp/tests/test_verification.py +++ b/fitapp/tests/test_verification.py @@ -3,9 +3,9 @@ https://dev.fitbit.com/docs/subscriptions/#verify-a-subscriber """ -from django.urls import reverse from django.test import TestCase from django.test.utils import override_settings +from django.urls import reverse class TestVerification(TestCase): diff --git a/fitapp/urls.py b/fitapp/urls.py index 0476c6c..c29b93f 100644 --- a/fitapp/urls.py +++ b/fitapp/urls.py @@ -2,7 +2,6 @@ from . import views - urlpatterns = [ # OAuth authentication url(r'^login/$', views.login, name='fitbit-login'), diff --git a/fitapp/utils.py b/fitapp/utils.py index 1cb8ca8..aacf173 100644 --- a/fitapp/utils.py +++ b/fitapp/utils.py @@ -1,10 +1,9 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured - from fitbit import Fitbit from . import defaults -from .models import UserFitbit, TimeSeriesDataType +from .models import TimeSeriesDataType, UserFitbit def create_fitbit(consumer_key=None, consumer_secret=None, **kwargs): diff --git a/fitapp/views.py b/fitapp/views.py index f61a5b4..b078263 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -1,26 +1,23 @@ -from functools import cmp_to_key import simplejson as json - from dateutil import parser from dateutil.relativedelta import relativedelta from django.contrib.auth.decorators import login_required from django.contrib.auth.signals import user_logged_in from django.core.exceptions import ImproperlyConfigured -from django.urls import reverse from django.dispatch import receiver -from django.http import HttpResponse, HttpResponseServerError, Http404 +from django.http import Http404, HttpResponse, HttpResponseServerError from django.shortcuts import redirect, render +from django.urls import reverse from django.utils import timezone from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_GET +from fitbit.exceptions import ( + HTTPConflict, HTTPForbidden, HTTPServerError, HTTPUnauthorized +) from six import string_types -from fitbit.exceptions import (HTTPUnauthorized, HTTPForbidden, HTTPConflict, - HTTPServerError) - -from . import forms -from . import utils -from .models import UserFitbit, TimeSeriesData, TimeSeriesDataType +from . import forms, utils +from .models import TimeSeriesData, TimeSeriesDataType, UserFitbit from .tasks import get_time_series_data, subscribe, unsubscribe @@ -127,7 +124,7 @@ def complete(request): tsdts = tsdts.filter(resource__in=res) # Sort as specified in FITAPP_SUBSCRIPTIONS tsdts = sorted(tsdts, key=lambda tsdt: ( - cats.index(tsdt.category) + res.index(tsdt.resource) + cats.index(tsdt.category) + res.index(tsdt.resource) )) # Create tasks for all data in all data types From 038f3d7e4e60106ec1495221b1336520e89f4b65 Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Mon, 18 Jun 2018 01:31:33 -0500 Subject: [PATCH 09/31] add Pipfile, update to Django 2.1 going to fix failing tests next --- Pipfile | 28 +++ Pipfile.lock | 443 +++++++++++++++++++++++++++++++++++++++++++ requirements/dev.txt | 9 - 3 files changed, 471 insertions(+), 9 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..7504eb6 --- /dev/null +++ b/Pipfile @@ -0,0 +1,28 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +fitbit = ">=0.3.0" +celery = "<4.2,>=4.1" +simplejson = "<4,>=3.13" +mock = "<3,>=2" +freezegun = "<0.4,>=0.3.9" +requests-mock = "<1.5,>=1.4.0" +Django = "<2.2,>=2.1a1" + +[dev-packages] +six = "*" +coverage = "*" +django-debug-toolbar = "*" +tox = "*" +Sphinx = "*" +pip-review = "*" +pipdeptree = "*" + +[requires] +python_version = "3.6" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..bd18a1f --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,443 @@ +{ + "_meta": { + "hash": { + "sha256": "e7b410a911113c685cafad5f4a2e2208cd829fdeb2748158bf4443548b257544" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "amqp": { + "hashes": [ + "sha256:073dd02fdd73041bffc913b767866015147b61f2a9bc104daef172fc1a0066eb", + "sha256:eed41946890cd43e8dee44a316b85cf6fee5a1a34bb4a562b660a358eb529e1b" + ], + "version": "==2.3.2" + }, + "billiard": { + "hashes": [ + "sha256:1d7b22bdc47aa52841120fcd22a74ae4fc8c13e9d3935643098184f5788c3ce6", + "sha256:abd9ce008c9a71ccde2c816f8daa36246e92a21e6a799831b887d88277187ecd" + ], + "version": "==3.5.0.3" + }, + "celery": { + "hashes": [ + "sha256:6fc4678d1692af97e137b2a9f1c04efd8e7e2fb7134c5c5ad60738cdd927762f", + "sha256:d1f2a3359bdbdfb344edce98b8e891f5fe64f8a11c5a45538ec20ac237c971f5" + ], + "index": "pypi", + "version": "==4.1.1" + }, + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "django": { + "hashes": [ + "sha256:89ac9fce84db947da7e5d1e7a24eabd21216c04cc4386c93197f0aa2487e9945" + ], + "index": "pypi", + "version": "==2.1a1" + }, + "fitbit": { + "hashes": [ + "sha256:9a88a790659c318762d1a30ad3d831393044f3040ca5d24959ca14eb3c1e256a" + ], + "index": "pypi", + "version": "==0.3.0" + }, + "freezegun": { + "hashes": [ + "sha256:703caac155dcaad61f78de4cb0666dca778d854dfb90b3699930adee0559a622", + "sha256:94c59d69bb99c9ec3ca5a3adb41930d3ea09d2a9756c23a02d89fa75646e78dd" + ], + "index": "pypi", + "version": "==0.3.10" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "kombu": { + "hashes": [ + "sha256:86adec6c60f63124e2082ea8481bbe4ebe04fde8ebed32c177c7f0cd2c1c9082", + "sha256:b274db3a4eacc4789aeb24e1de3e460586db7c4fc8610f7adcc7a3a1709a60af" + ], + "version": "==4.2.1" + }, + "mock": { + "hashes": [ + "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", + "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "oauthlib": { + "hashes": [ + "sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162", + "sha256:d883b36b21a6ad813953803edfa563b1b579d79ca758fe950d1bc9e8b326025b" + ], + "version": "==2.1.0" + }, + "pbr": { + "hashes": [ + "sha256:3747c6f017f2dc099986c325239661948f9f5176f6880d9fdef164cb664cd665", + "sha256:a9c27eb8f0e24e786e544b2dbaedb729c9d8546342b5a6818d8eda098ad4340d" + ], + "version": "==4.0.4" + }, + "python-dateutil": { + "hashes": [ + "sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0", + "sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8" + ], + "version": "==2.7.3" + }, + "pytz": { + "hashes": [ + "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", + "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" + ], + "version": "==2018.4" + }, + "requests": { + "hashes": [ + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + ], + "version": "==2.19.1" + }, + "requests-mock": { + "hashes": [ + "sha256:2931887853c42e1d73879983d5bf03041109472991c5b4b8dba5d11ed23b9d0b", + "sha256:96a1e45b1c0bd18d14fcb2d55b3b09d6d46237e37bcae3155df4cb75bc42619e" + ], + "index": "pypi", + "version": "==1.4.0" + }, + "requests-oauthlib": { + "hashes": [ + "sha256:8886bfec5ad7afb391ed5443b1f697c6f4ae98d0e5620839d8b4499c032ada3f", + "sha256:e21232e2465808c0e892e0e4dbb8c2faafec16ac6dc067dd546e9b466f3deac8", + "sha256:fe3282f48fb134ee0035712159f5429215459407f6d5484013343031ff1a400d" + ], + "version": "==1.0.0" + }, + "simplejson": { + "hashes": [ + "sha256:1bdd7c7c8c3ece26a251c835e73627a5f825b6ac1d16a68f190c8c29a3a4d4fe", + "sha256:1e651e49b91024e615267fe800ad094c1174800ff06a3b29652f4a656dc51228", + "sha256:2fa4eafab7cb4f900ce7739129cef0da1f25acfa89089540ce6241a15a61df78", + "sha256:346ef48e38d202634ef5f8402d3e043984ef9f504f00b2275807cc8a01ae7d31", + "sha256:446cc58ef7d8a4c5cb336d6893fd6aca1c22800207c18aa72f1496798e2aa6e8", + "sha256:523d3df8dd6a366f8911ffdd9778d2e52d174f1b2ae867c543f9f200ab06595d", + "sha256:60427da697809f2ec0a49b1c9146bf858722bd04fabef77b2b4fca0e883595ef", + "sha256:7418a069e046df6afc9024076a154d35c4df432ad081f2a1cd57661c9dc95b51", + "sha256:839ce3128375c4b6a08b4d20e67befe8fc0d6cf7ed1a1ee6117f035225368de8", + "sha256:8b7644d71f8fb11088660775da0ab09151583939edeb840a53bdf2c5acf3b725", + "sha256:95d19832c666c5942c57e67132138a9332aa84519919a97bd9bbcff1fce7cefd", + "sha256:97502022f2fe5cf78580d5b0889030a62f46080508d32a0992ee7d7d107790b8", + "sha256:a0bdc46d207edaa1db128be6f5b84e415aa033a854ed02d40256fc12d72ce0d7", + "sha256:ad332f65d9551ceffc132d0a683f4ffd12e4bc7538681100190d577ced3473fb", + "sha256:b5263de68cd0891ff4fe8ceb14e6382635ffb1be2ee0c8f7e664681679cd8163", + "sha256:bb68f637d12dccf7ddeece29b4a5f27f15b768dff7f02c198d3d7640f9b8d2dc", + "sha256:d6b38d952a3e90022287998928b12a77471b054455bcded9731dfb8371c47df8", + "sha256:d7e1191ddccabcdc5b300d4469e0fbe69abda6d5e1fba37dad66f8a9913a9994", + "sha256:e22f4bda9177893eb89a6a3cfc5335ae393690407b9b3407e86fe47c2a4adb1d", + "sha256:f00fb192452506454ce7dba6de5a0d5386631e1d6cbc8dcb7e7d4b220bb13c06", + "sha256:fae2430550b625ab2284110f4802ed1a1cae45f96871afbb014ee10f30a37fa3" + ], + "index": "pypi", + "version": "==3.15.0" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "urllib3": { + "hashes": [ + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + ], + "version": "==1.23" + }, + "vine": { + "hashes": [ + "sha256:52116d59bc45392af9fdd3b75ed98ae48a93e822cee21e5fda249105c59a7a72", + "sha256:6849544be74ec3638e84d90bc1cf2e1e9224cc10d96cd4383ec3f69e9bce077b" + ], + "version": "==1.1.4" + } + }, + "develop": { + "alabaster": { + "hashes": [ + "sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732", + "sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0" + ], + "version": "==0.7.10" + }, + "babel": { + "hashes": [ + "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", + "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" + ], + "version": "==2.6.0" + }, + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "coverage": { + "hashes": [ + "sha256:10cfac276cf3dd0acefc49444fc4e1a0a4c23c855d9fcbd555681c3a47a328e6", + "sha256:18797137634b64fe488b239d3709e5f8fdea80aea09f86ec819c633a2c84f79c", + "sha256:1a54b37e265dd81922f32eff50559630905770cdf8a8e560aa5a4f3297e5d5bf", + "sha256:245709d580be9c7a5f8e2aeebab889f571ac323ff34bdde497072e82c0295546", + "sha256:316881a28d2a1a5853495092267fcacf245805b4139f0fc996f8a6c4be6fb499", + "sha256:3368098e2c633ec6b2af4f91abde94b5c3b8fa66857452137485f40be77aeda6", + "sha256:33e0aa553d256b0daf43e0026db3bd415eb4b94c8dc7984afb84c10efa51a83b", + "sha256:35fe7a6c06851c4c6a4c171eb796d27e023f5a1ce1e25837ea720f5b8cb76fce", + "sha256:3a1c8ed67a64627ef317de64356731f8f173b76457672e933db896c080e1cc2b", + "sha256:3e79318f0ddb197e775a742cc44807b1e9f3b8a57325f422fe547d3e0ca01b86", + "sha256:59fa7e9857205b8d6f6fce0eaea07409bcdffd68eaec3db7e0b1ac720d4fe0f3", + "sha256:6b2e2ef7572b399b0cc2f6d05c06ada40329166d6fc58beef8081fb94a41201f", + "sha256:712599fc602c302c540fe7e83b6d82aaf381ec5bfb4a51dc5c30f57d214d649f", + "sha256:773c0e658503538554516f5f901e775cda760648d8d2b988e16f187812c0c089", + "sha256:7c8dbbc9e5480856125511f11a5c735cff3200e367adc3ba342dad506a25407d", + "sha256:7fc25906ecb0a6af0c434370da6cfbcf8badb257c5cf9a6464f5e37fe4ebc949", + "sha256:88d81556e00ac7e1cc9e70a2376859f41e46d187b6dd5883422aa537505f8a98", + "sha256:91a915f5fc88db7adace367e8ef65d1a418d29f7ade62514d604eed87c861355", + "sha256:9f696b90ff4886ba5a277995397a13b0600bfd97c70d8ae4241c2aecea11ee61", + "sha256:a863f4540446d7eeaf6bf716aee277eaf38842718e86bdb80cdca78cdf1fed0d", + "sha256:ab3981817dcec2dd9ea552e46538ee2e34480ec623fc365019ddae82bc9be143", + "sha256:b3b6d8d8194e7e1300240402dfd9c54840d03621e69da821d8ffc8bbebe00137", + "sha256:c296ac03ba12e184bef03387d89c4a0be79daff214294917ce77df32240bf4d8", + "sha256:c75b3de73cc7ba2e911a907322c65dd10da216f37e7477f22dbd0098775f6345", + "sha256:c87c9ee13ce431305734b8e3f0bf00468a1d4f4ee60b6ef63c69282776ab94d6", + "sha256:c89c895ff5cfda45a5f681514b647986f76a4f984df125d210c154e5a1a2472b", + "sha256:c9fa8fbda281b1ddf25b8fa7ccf0564198a86c9da8a413111fcadd510a98a232", + "sha256:ccdf1bd8fd848690fb3d5153d0c54c41169e59804acb9652664f5f669fe25c11" + ], + "index": "pypi", + "version": "==5.0a1" + }, + "django": { + "hashes": [ + "sha256:89ac9fce84db947da7e5d1e7a24eabd21216c04cc4386c93197f0aa2487e9945" + ], + "index": "pypi", + "version": "==2.1a1" + }, + "django-debug-toolbar": { + "hashes": [ + "sha256:4af2a4e1e932dadbda197b18585962d4fc20172b4e5a479490bc659fe998864d", + "sha256:d9ea75659f76d8f1e3eb8f390b47fc5bad0908d949c34a8a3c4c87978eb40a0f" + ], + "index": "pypi", + "version": "==1.9.1" + }, + "docutils": { + "hashes": [ + "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + ], + "version": "==0.14" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "imagesize": { + "hashes": [ + "sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18", + "sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315" + ], + "version": "==1.0.0" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "version": "==1.0" + }, + "packaging": { + "hashes": [ + "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0", + "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b" + ], + "version": "==17.1" + }, + "pip-review": { + "hashes": [ + "sha256:aa368c457e4b097d0cfbf9024eb574c57883177bc38d89c75eafc4e58174a82e", + "sha256:e4b27cdf0d74b519abc792eb4ab0d92471f1a378efa0f5e382ff9c7ad69916ab" + ], + "index": "pypi", + "version": "==1.0" + }, + "pipdeptree": { + "hashes": [ + "sha256:362c5877b3b1641671ea05b99d068e86b5cf8b848e8affa8a4af67aa18762d46", + "sha256:65a4aff5d58c4f30d38dc3c1f7b9c6fa4b2e528a2f68a4957196e62f91f3dd62", + "sha256:6683e1779d15828cf42f269e168a3f8559aef28a0c176d5ed85b50cd46634078" + ], + "index": "pypi", + "version": "==0.12.1" + }, + "pluggy": { + "hashes": [ + "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", + "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", + "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" + ], + "version": "==0.6.0" + }, + "py": { + "hashes": [ + "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", + "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" + ], + "version": "==1.5.3" + }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + ], + "version": "==2.2.0" + }, + "pyparsing": { + "hashes": [ + "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", + "sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07", + "sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18", + "sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e", + "sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5", + "sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58", + "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" + ], + "version": "==2.2.0" + }, + "pytz": { + "hashes": [ + "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", + "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" + ], + "version": "==2018.4" + }, + "requests": { + "hashes": [ + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + ], + "version": "==2.19.1" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", + "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" + ], + "version": "==1.2.1" + }, + "sphinx": { + "hashes": [ + "sha256:85f7e32c8ef07f4ba5aeca728e0f7717bef0789fba8458b8d9c5c294cad134f3", + "sha256:d45480a229edf70d84ca9fae3784162b1bc75ee47e480ffe04a4b7f21a95d76d" + ], + "index": "pypi", + "version": "==1.7.5" + }, + "sphinxcontrib-websupport": { + "hashes": [ + "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", + "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" + ], + "version": "==1.1.0" + }, + "sqlparse": { + "hashes": [ + "sha256:ce028444cfab83be538752a2ffdb56bc417b7784ff35bb9a3062413717807dec", + "sha256:d9cf190f51cbb26da0412247dfe4fb5f4098edb73db84e02f9fc21fdca31fed4" + ], + "version": "==0.2.4" + }, + "tox": { + "hashes": [ + "sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f", + "sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0" + ], + "index": "pypi", + "version": "==3.0.0" + }, + "urllib3": { + "hashes": [ + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + ], + "version": "==1.23" + }, + "virtualenv": { + "hashes": [ + "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", + "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" + ], + "version": "==16.0.0" + } + } +} diff --git a/requirements/dev.txt b/requirements/dev.txt index 306bcf7..e69de29 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,9 +0,0 @@ --r base.txt --r test.txt - -django-debug-toolbar -Django>=2.0.1,<2.1 -tox>=2.9,<3 - -pip-review -pipdeptree \ No newline at end of file From 6485428af0a492085cb3d6c153399cc2a46fd97f Mon Sep 17 00:00:00 2001 From: Kevin Blicharski Date: Mon, 18 Jun 2018 01:43:12 -0500 Subject: [PATCH 10/31] revert to Django 2.0.6 --- Pipfile | 2 +- Pipfile.lock | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Pipfile b/Pipfile index 7504eb6..218f78c 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ simplejson = "<4,>=3.13" mock = "<3,>=2" freezegun = "<0.4,>=0.3.9" requests-mock = "<1.5,>=1.4.0" -Django = "<2.2,>=2.1a1" +Django = "<2.1,>=2.0" [dev-packages] six = "*" diff --git a/Pipfile.lock b/Pipfile.lock index bd18a1f..c4f68be 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e7b410a911113c685cafad5f4a2e2208cd829fdeb2748158bf4443548b257544" + "sha256": "6f216cb394446c9bf77c402fdef17abd64d0e53be4318ed3902fa5a31227574c" }, "pipfile-spec": 6, "requires": { @@ -54,10 +54,11 @@ }, "django": { "hashes": [ - "sha256:89ac9fce84db947da7e5d1e7a24eabd21216c04cc4386c93197f0aa2487e9945" + "sha256:3eb25c99df1523446ec2dc1b00e25eb2ecbdf42c9d8b0b8b32a204a8db9011f8", + "sha256:69ff89fa3c3a8337015478a1a0744f52a9fef5d12c1efa01a01f99bcce9bf10c" ], "index": "pypi", - "version": "==2.1a1" + "version": "==2.0.6" }, "fitbit": { "hashes": [ @@ -261,10 +262,11 @@ }, "django": { "hashes": [ - "sha256:89ac9fce84db947da7e5d1e7a24eabd21216c04cc4386c93197f0aa2487e9945" + "sha256:3eb25c99df1523446ec2dc1b00e25eb2ecbdf42c9d8b0b8b32a204a8db9011f8", + "sha256:69ff89fa3c3a8337015478a1a0744f52a9fef5d12c1efa01a01f99bcce9bf10c" ], "index": "pypi", - "version": "==2.1a1" + "version": "==2.0.6" }, "django-debug-toolbar": { "hashes": [ From f451d561a22245baebd96332b3ea8ff0bf3d13d8 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Thu, 5 Jul 2018 15:35:32 -0500 Subject: [PATCH 11/31] Add doc for recording intraday changes and add intraday-related setting. --- fitapp/defaults.py | 5 +++++ intraday-support-docs.md | 11 +++++++++++ test_settings.py | 1 + 3 files changed, 17 insertions(+) create mode 100644 intraday-support-docs.md diff --git a/fitapp/defaults.py b/fitapp/defaults.py index 66d8f7f..aca1514 100644 --- a/fitapp/defaults.py +++ b/fitapp/defaults.py @@ -41,3 +41,8 @@ # called with the request as the only parameter to get the final value for the # message. FITAPP_DECORATOR_MESSAGE = 'This page requires Fitbit integration.' + +# By default, don't try to get intraday time series data. See +# https://dev.fitbit.com/docs/activity/#get-activity-intraday-time-series for +# more info. +FITAPP_GET_INTRADAY = False diff --git a/intraday-support-docs.md b/intraday-support-docs.md new file mode 100644 index 0000000..30ccb99 --- /dev/null +++ b/intraday-support-docs.md @@ -0,0 +1,11 @@ +How to get intraday support to work + +- There must be a FITAPP_GET_INTRADAY boolean setting somewhere in the Django project's settings files. +If True, django-fitbit will retrieve and create records for intraday data for any TimeSeriesDataType marked +as intraday-compatible. +- When TimeSeriesDataTypes are created, they must be given + +What was changed +- Add FITAPP_GET_INTRADAY setting to: + - fitapp/defaults.py - False + - test_settings.py - True \ No newline at end of file diff --git a/test_settings.py b/test_settings.py index 57b9ca9..a41e667 100644 --- a/test_settings.py +++ b/test_settings.py @@ -21,6 +21,7 @@ FITAPP_CONSUMER_SECRET = 'FAKE_CONSUMER_KEY' FITAPP_SUBSCRIBE = True FITAPP_SUBSCRIBER_ID = 1 +FITAPP_GET_INTRADAY = True LOGGING = { 'version': 1, From 90ebad466a98d9ff02e30427491dc184cb9f77ee Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Thu, 5 Jul 2018 15:44:21 -0500 Subject: [PATCH 12/31] Make and document intraday changes to fitapp/models.py. --- fitapp/models.py | 6 ++++-- intraday-support-docs.md | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/fitapp/models.py b/fitapp/models.py index 78a134e..ae7dc0f 100644 --- a/fitapp/models.py +++ b/fitapp/models.py @@ -72,6 +72,7 @@ class TimeSeriesDataType(models.Model): 'be used for the [resource-path] of the API url referred to in ' 'the Fitbit documentation' )) + intraday_support = models.BooleanField(default=False) def __str__(self): return self.path() @@ -104,7 +105,7 @@ class TimeSeriesData(models.Model): help_text='The type of time series data', on_delete=models.CASCADE ) - date = models.DateField(help_text='The date the data was recorded') + date = models.DateTimeField(help_text='The date the data was recorded, and time if intraday.') value = models.CharField( null=True, default=None, @@ -115,9 +116,10 @@ class TimeSeriesData(models.Model): 'For example, for step data the value might be "9783" (the units) ' 'would be "steps"' )) + intraday = models.BooleanField(default=False) class Meta: - unique_together = ('user', 'resource_type', 'date') + unique_together = ('user', 'resource_type', 'date', 'intraday') def string_date(self): return self.date.strftime('%Y-%m-%d') diff --git a/intraday-support-docs.md b/intraday-support-docs.md index 30ccb99..d3cdbb7 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -3,9 +3,21 @@ How to get intraday support to work - There must be a FITAPP_GET_INTRADAY boolean setting somewhere in the Django project's settings files. If True, django-fitbit will retrieve and create records for intraday data for any TimeSeriesDataType marked as intraday-compatible. -- When TimeSeriesDataTypes are created, they must be given +- When TimeSeriesDataTypes are created, they must be given intraday_support = True + - VERY IMPORTANT: There are not currently checks to see if the given Fitbit app has authorization for + a certain category of intraday data. Be sure your app has authorization before attempting to retrieve it. + +- Intraday TimeSeriesData instances will be marked with intraday = True. + What was changed - Add FITAPP_GET_INTRADAY setting to: - fitapp/defaults.py - False - - test_settings.py - True \ No newline at end of file + - test_settings.py - True + +- Changes to fitapp/models.py + - Add intraday_support field to TimeSeriesDataType, defaults to False. + - Add intraday field to TimeSeriesData, defaults to False. + - Change date field for TimeSeriesData from DateField to DateTimeField, change help text to reflect this. + - NOTE: I would like to change the name of this field to date_time. + - Change unique_together values for TimeSeriesData by adding 'intraday' as a requirement. \ No newline at end of file From e7f98a35120c559653a87aed8c04a79e263fcc04 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Tue, 10 Jul 2018 19:14:12 -0500 Subject: [PATCH 13/31] Small changes and additional notes. --- fitapp/tasks.py | 5 +++++ fitapp/views.py | 2 +- intraday-support-docs.md | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/fitapp/tasks.py b/fitapp/tasks.py index 26ed095..bf1aa7e 100644 --- a/fitapp/tasks.py +++ b/fitapp/tasks.py @@ -106,3 +106,8 @@ def get_time_series_data(self, fitbit_user, cat, resource, date=None): except Exception as e: logger.exception("Exception updating data: %s" % e) raise Reject(e, requeue=False) + + +@shared_task +def get_intraday_data(): + pass diff --git a/fitapp/views.py b/fitapp/views.py index b078263..2dda83d 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -18,7 +18,7 @@ from . import forms, utils from .models import TimeSeriesData, TimeSeriesDataType, UserFitbit -from .tasks import get_time_series_data, subscribe, unsubscribe +from .tasks import get_time_series_data, subscribe, unsubscribe, get_intraday_data @login_required diff --git a/intraday-support-docs.md b/intraday-support-docs.md index d3cdbb7..b17ccc5 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -20,4 +20,18 @@ What was changed - Add intraday field to TimeSeriesData, defaults to False. - Change date field for TimeSeriesData from DateField to DateTimeField, change help text to reflect this. - NOTE: I would like to change the name of this field to date_time. - - Change unique_together values for TimeSeriesData by adding 'intraday' as a requirement. \ No newline at end of file + - Change unique_together values for TimeSeriesData by adding 'intraday' as a requirement. + +Changes to fitapp/migrations + +- Changes to fitapp/views.py + - Change + +- Changes to fitapp/tasks.py + - Add currently empty get_intraday_data task. + + +Planned changes +Have fitbit listener view call a different task depending on if data is intraday or not. +Probably need to add fitapp subscriptions value for test settings? +issues with migrations \ No newline at end of file From 0b9ecb1ce91dbfe8fa3778c7a039367ba1f17ea4 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Tue, 10 Jul 2018 19:33:39 -0500 Subject: [PATCH 14/31] Make changes to migrations files to correctly add new intraday related fields. --- fitapp/fixtures/initial_data.json | 108 ++++++++++++------- fitapp/migrations/0001_initial.py | 1 + fitapp/migrations/0010_auto_20180710_1846.py | 29 +++++ 3 files changed, 102 insertions(+), 36 deletions(-) create mode 100644 fitapp/migrations/0010_auto_20180710_1846.py diff --git a/fitapp/fixtures/initial_data.json b/fitapp/fixtures/initial_data.json index 49f53c0..ef9381b 100644 --- a/fitapp/fixtures/initial_data.json +++ b/fitapp/fixtures/initial_data.json @@ -4,7 +4,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "calories" + "resource": "calories", + "intraday_support": "False" } }, { @@ -12,7 +13,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "caloriesBMR" + "resource": "caloriesBMR", + "intraday_support": "False" } }, { @@ -20,7 +22,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "steps" + "resource": "steps", + "intraday_support": "False" } }, { @@ -28,7 +31,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "distance" + "resource": "distance", + "intraday_support": "False" } }, { @@ -36,7 +40,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "floors" + "resource": "floors", + "intraday_support": "False" } }, { @@ -44,7 +49,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "elevation" + "resource": "elevation", + "intraday_support": "False" } }, { @@ -52,7 +58,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "minutesSedentary" + "resource": "minutesSedentary", + "intraday_support": "False" } }, { @@ -60,7 +67,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "minutesLightlyActive" + "resource": "minutesLightlyActive", + "intraday_support": "False" } }, { @@ -68,7 +76,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "minutesFairlyActive" + "resource": "minutesFairlyActive", + "intraday_support": "False" } }, { @@ -76,7 +85,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "minutesVeryActive" + "resource": "minutesVeryActive", + "intraday_support": "False" } }, { @@ -84,7 +94,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "activeScore" + "resource": "activeScore", + "intraday_support": "False" } }, { @@ -92,7 +103,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "activityCalories" + "resource": "activityCalories", + "intraday_support": "False" } }, { @@ -100,7 +112,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/minutesSedentary" + "resource": "tracker/minutesSedentary", + "intraday_support": "False" } }, { @@ -108,7 +121,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/minutesLightlyActive" + "resource": "tracker/minutesLightlyActive", + "intraday_support": "False" } }, { @@ -116,7 +130,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/minutesFairlyActive" + "resource": "tracker/minutesFairlyActive", + "intraday_support": "False" } }, { @@ -124,7 +139,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/minutesVeryActive" + "resource": "tracker/minutesVeryActive", + "intraday_support": "False" } }, { @@ -132,7 +148,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/activeScore" + "resource": "tracker/activeScore", + "intraday_support": "False" } }, { @@ -140,7 +157,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/activityCalories" + "resource": "tracker/activityCalories", + "intraday_support": "False" } }, { @@ -148,7 +166,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/calories" + "resource": "tracker/calories", + "intraday_support": "False" } }, { @@ -156,7 +175,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/steps" + "resource": "tracker/steps", + "intraday_support": "False" } }, { @@ -164,7 +184,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/distance" + "resource": "tracker/distance", + "intraday_support": "False" } }, { @@ -172,7 +193,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/floors" + "resource": "tracker/floors", + "intraday_support": "False" } }, { @@ -180,7 +202,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 1, - "resource": "tracker/elevation" + "resource": "tracker/elevation", + "intraday_support": "False" } }, { @@ -188,7 +211,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 3, - "resource": "weight" + "resource": "weight", + "intraday_support": "False" } }, { @@ -196,7 +220,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 3, - "resource": "bmi" + "resource": "bmi", + "intraday_support": "False" } }, { @@ -204,7 +229,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 3, - "resource": "fat" + "resource": "fat", + "intraday_support": "False" } }, { @@ -212,7 +238,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 2, - "resource": "startTime" + "resource": "startTime", + "intraday_support": "False" } }, { @@ -220,7 +247,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 2, - "resource": "timeInBed" + "resource": "timeInBed", + "intraday_support": "False" } }, { @@ -228,7 +256,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 2, - "resource": "minutesAsleep" + "resource": "minutesAsleep", + "intraday_support": "False" } }, { @@ -236,7 +265,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 2, - "resource": "awakeningsCount" + "resource": "awakeningsCount", + "intraday_support": "False" } }, { @@ -244,7 +274,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 2, - "resource": "minutesAwake" + "resource": "minutesAwake", + "intraday_support": "False" } }, { @@ -252,7 +283,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 2, - "resource": "minutesToFallAsleep" + "resource": "minutesToFallAsleep", + "intraday_support": "False" } }, { @@ -260,7 +292,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 2, - "resource": "minutesAfterWakeup" + "resource": "minutesAfterWakeup", + "intraday_support": "False" } }, { @@ -268,7 +301,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 2, - "resource": "efficiency" + "resource": "efficiency", + "intraday_support": "False" } }, { @@ -276,7 +310,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 0, - "resource": "log/caloriesIn" + "resource": "log/caloriesIn", + "intraday_support": "False" } }, { @@ -284,7 +319,8 @@ "model": "fitapp.timeseriesdatatype", "fields": { "category": 0, - "resource": "log/water" + "resource": "log/water", + "intraday_support": "False" } } ] diff --git a/fitapp/migrations/0001_initial.py b/fitapp/migrations/0001_initial.py index aaa069a..edeea24 100644 --- a/fitapp/migrations/0001_initial.py +++ b/fitapp/migrations/0001_initial.py @@ -34,6 +34,7 @@ class Migration(migrations.Migration): choices=[(0, b'foods'), (1, b'activities'), (2, b'sleep'), (3, b'body')])), ('resource', models.CharField(max_length=128)), + ('intraday_support', models.BooleanField(default=False)), ], options={ 'ordering': ['category', 'resource'], diff --git a/fitapp/migrations/0010_auto_20180710_1846.py b/fitapp/migrations/0010_auto_20180710_1846.py new file mode 100644 index 0000000..15c3f29 --- /dev/null +++ b/fitapp/migrations/0010_auto_20180710_1846.py @@ -0,0 +1,29 @@ +# Generated by Django 2.0.6 on 2018-07-10 18:46 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('fitapp', '0009_auto_20180110_1605'), + ] + + operations = [ + migrations.AddField( + model_name='timeseriesdata', + name='intraday', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='timeseriesdata', + name='date', + field=models.DateTimeField(help_text='The date the data was recorded, and time if intraday.'), + ), + migrations.AlterUniqueTogether( + name='timeseriesdata', + unique_together={('user', 'resource_type', 'date', 'intraday')}, + ), + ] From 320e9bd5ba651dafabc3dad825211a3e293d548c Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Tue, 10 Jul 2018 19:40:15 -0500 Subject: [PATCH 15/31] More notes on intraday support. --- intraday-support-docs.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/intraday-support-docs.md b/intraday-support-docs.md index b17ccc5..655214f 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -23,6 +23,8 @@ What was changed - Change unique_together values for TimeSeriesData by adding 'intraday' as a requirement. Changes to fitapp/migrations + - Move addition of TimeSeriesDataType.intraday_support to 0001_initial.py to resolve errors. + - Add intraday_support field to every TimeSeriesDataType in fitapp/fixtures/initial_data.json to resolve errors. - Changes to fitapp/views.py - Change @@ -33,5 +35,4 @@ Changes to fitapp/migrations Planned changes Have fitbit listener view call a different task depending on if data is intraday or not. -Probably need to add fitapp subscriptions value for test settings? -issues with migrations \ No newline at end of file +Probably need to add fitapp subscriptions value for test settings? \ No newline at end of file From def759f076b8ae2c96ace06dc2676fe4cf832d2b Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Thu, 12 Jul 2018 08:20:29 -0500 Subject: [PATCH 16/31] Change task scheduling portion of subscription view to use get_intraday_data when appropriate. --- fitapp/views.py | 5 +++++ intraday-support-docs.md | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/fitapp/views.py b/fitapp/views.py index 2dda83d..428a984 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -264,6 +264,11 @@ def update(request): key=lambda tsdt: res_list.index(tsdt.resource) ) for i, _type in enumerate(tsdts): + if utils.get_setting('FITAPP_GET_INTRADAY') and _type.intraday_support: + get_intraday_data.apply.async( + (update['ownerId'], _type.category, _type.resource,), + {'date': parser.parse(update['date'])}, + countdown=(btw_delay * i)) # Offset each call by a few seconds so they don't bog down # the server get_time_series_data.apply_async( diff --git a/intraday-support-docs.md b/intraday-support-docs.md index 655214f..5664bf9 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -1,12 +1,36 @@ +Summary for 7/12/18 +I'm still trying to get a handle on what changed between the original django-fitbit and our fork. +Particularly the FITAPP_SUBSCRIPTIONS setting in the original django-fitbit - there are no usages of this in the +old MapTrek, and it defaults to None, which would seem to make some of the code not work, but it does. +The old MapTrek uses a setting called FITAPP_SUBSCRIPTION_COLLECTION to enumerate what categories of data +to subscribe to, and this is used when subscriptions are created but i can't exactly figure out +what its role is in actually getting data. + +Investigating further I found that FITAPP_SUBSCRIPTIONS is not set in the old Maptrek. (set to None) + + +When we get a subscription notification, it looks like this: +{ + "collectionType": "activities", + "date": "2010-03-01", + "ownerId": "184X36", + "ownerType": "user", + "subscriptionId": "2345" + } +Note that it doesn't specify what kind of activity data is available. Since we only care about steps, +we may get activity notifications that end up being useless because they're not for step data. I'm not sure +but this may be a reason behind the dropped data issue. + + How to get intraday support to work - There must be a FITAPP_GET_INTRADAY boolean setting somewhere in the Django project's settings files. If True, django-fitbit will retrieve and create records for intraday data for any TimeSeriesDataType marked as intraday-compatible. +- FITAPP_GET_INTRADAY defaults to False. - When TimeSeriesDataTypes are created, they must be given intraday_support = True - VERY IMPORTANT: There are not currently checks to see if the given Fitbit app has authorization for a certain category of intraday data. Be sure your app has authorization before attempting to retrieve it. - - Intraday TimeSeriesData instances will be marked with intraday = True. @@ -27,7 +51,8 @@ Changes to fitapp/migrations - Add intraday_support field to every TimeSeriesDataType in fitapp/fixtures/initial_data.json to resolve errors. - Changes to fitapp/views.py - - Change + - Change the task scheduling portion of the update view to schedule the get_intraday_data task + when the data needed is intraday enabled, instead of calling get_time_series_data. - Changes to fitapp/tasks.py - Add currently empty get_intraday_data task. @@ -35,4 +60,8 @@ Changes to fitapp/migrations Planned changes Have fitbit listener view call a different task depending on if data is intraday or not. -Probably need to add fitapp subscriptions value for test settings? \ No newline at end of file +Probably need to add fitapp subscriptions value for test settings? + +Unknown number of new settings introduced in fork of django-fitbit +I have yet to figure out how FITAPP_SUBSCRIPTIONS is set in Maptrek. +Fork introduces FITAPP_SUBSCRIPTION_COLLECTION From efcfb824e5f527a1bd568dbba6c81ed2b90de7c7 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Thu, 12 Jul 2018 08:25:35 -0500 Subject: [PATCH 17/31] Update intraday-support-docs.md --- intraday-support-docs.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/intraday-support-docs.md b/intraday-support-docs.md index 5664bf9..a71e04f 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -1,4 +1,5 @@ Summary for 7/12/18 + I'm still trying to get a handle on what changed between the original django-fitbit and our fork. Particularly the FITAPP_SUBSCRIPTIONS setting in the original django-fitbit - there are no usages of this in the old MapTrek, and it defaults to None, which would seem to make some of the code not work, but it does. @@ -59,9 +60,17 @@ Changes to fitapp/migrations Planned changes + +- Add last_intraday_data_from_when or similar field to UserFitbit +- Rewrite get_intraday_data to use appropriate python-fitbit function and to update/maintain last_intraday_data field. + - Also to ask for a time range instead of a full day of data. + Have fitbit listener view call a different task depending on if data is intraday or not. Probably need to add fitapp subscriptions value for test settings? + + + Unknown number of new settings introduced in fork of django-fitbit I have yet to figure out how FITAPP_SUBSCRIPTIONS is set in Maptrek. Fork introduces FITAPP_SUBSCRIPTION_COLLECTION From 822241033fe0a1c822b4ec430994a375ecc50dda Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Thu, 12 Jul 2018 11:37:23 -0500 Subject: [PATCH 18/31] Add field to track last intraday step data. --- .gitignore | 4 +++- ...rfitbit_last_intraday_step_data_datetime.py | 18 ++++++++++++++++++ fitapp/migrations/0012_auto_20180712_1132.py | 18 ++++++++++++++++++ fitapp/models.py | 8 ++++++++ intraday-support-docs.md | 1 + 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 fitapp/migrations/0011_userfitbit_last_intraday_step_data_datetime.py create mode 100644 fitapp/migrations/0012_auto_20180712_1132.py diff --git a/.gitignore b/.gitignore index e4fa4c7..79c08f0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ docs/_build/ .coverage htmlcov/ *~ -.idea \ No newline at end of file +.idea + +django_fitapp \ No newline at end of file diff --git a/fitapp/migrations/0011_userfitbit_last_intraday_step_data_datetime.py b/fitapp/migrations/0011_userfitbit_last_intraday_step_data_datetime.py new file mode 100644 index 0000000..0f1f856 --- /dev/null +++ b/fitapp/migrations/0011_userfitbit_last_intraday_step_data_datetime.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.6 on 2018-07-12 11:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fitapp', '0010_auto_20180710_1846'), + ] + + operations = [ + migrations.AddField( + model_name='userfitbit', + name='last_intraday_step_data_datetime', + field=models.DateTimeField(default=None), + ), + ] diff --git a/fitapp/migrations/0012_auto_20180712_1132.py b/fitapp/migrations/0012_auto_20180712_1132.py new file mode 100644 index 0000000..8eba93b --- /dev/null +++ b/fitapp/migrations/0012_auto_20180712_1132.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.6 on 2018-07-12 11:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fitapp', '0011_userfitbit_last_intraday_step_data_datetime'), + ] + + operations = [ + migrations.AlterField( + model_name='userfitbit', + name='last_intraday_step_data_datetime', + field=models.DateTimeField(blank=True, default=None, null=True), + ), + ] diff --git a/fitapp/models.py b/fitapp/models.py index ae7dc0f..785f36d 100644 --- a/fitapp/models.py +++ b/fitapp/models.py @@ -20,6 +20,14 @@ class UserFitbit(models.Model): expires_at = models.FloatField( help_text='The timestamp when the access token expires') + # Essentially a MapTrek-specific field. + # If the app collects intraday step data, this field stores the datetime of the last + # piece of data taken from Fitbit, zero or nonzero. + last_intraday_step_data_datetime = models.DateTimeField( + null=True, + blank=True, + default=None) + def __str__(self): return self.user.__str__() diff --git a/intraday-support-docs.md b/intraday-support-docs.md index 5664bf9..9d2906a 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -42,6 +42,7 @@ What was changed - Changes to fitapp/models.py - Add intraday_support field to TimeSeriesDataType, defaults to False. - Add intraday field to TimeSeriesData, defaults to False. + - Add last_intraday_step_data_datetime field to UserFitbit, defaults to None. - Change date field for TimeSeriesData from DateField to DateTimeField, change help text to reflect this. - NOTE: I would like to change the name of this field to date_time. - Change unique_together values for TimeSeriesData by adding 'intraday' as a requirement. From e72a88fe725e737ad82226a9108846a01b6b873b Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Fri, 13 Jul 2018 16:46:58 -0500 Subject: [PATCH 19/31] Beign to make changes to data-pulling chain of events to allow for intraday data. --- fitapp/tasks.py | 75 ++++++++++++++++++++++++++++++++++++++-- fitapp/utils.py | 15 +++++--- fitapp/views.py | 13 +++---- intraday-support-docs.md | 7 +++- 4 files changed, 96 insertions(+), 14 deletions(-) diff --git a/fitapp/tasks.py b/fitapp/tasks.py index bf1aa7e..9e7002a 100644 --- a/fitapp/tasks.py +++ b/fitapp/tasks.py @@ -108,6 +108,75 @@ def get_time_series_data(self, fitbit_user, cat, resource, date=None): raise Reject(e, requeue=False) -@shared_task -def get_intraday_data(): - pass +def get_intraday_data(fitbit_user, cat, resource, date, tz_offset, start_time=None, end_time=None): + """ + Get the user's intraday data for a specified date, convert to UTC prior to + saving. + + The Fitbit API stipulates that intraday data can only be retrieved for one + day at a time. + """ + try: + _type = TimeSeriesDataType.objects.get(category=cat, resource=resource) + except TimeSeriesDataType.DoesNotExist: + logger.exception("The resource %s in category %s doesn't exist" % + (resource, cat)) + raise Reject(sys.exc_info()[1], requeue=False) + if not _type.intraday_support: + logger.exception("The resource %s in category %s does not support " + "intraday time series" % (resource, cat)) + raise Reject(sys.exc_info()[1], requeue=False) + + # Create a lock so we don't try to run the same task multiple times + sdat = date.strftime('%Y-%m-%d') + + fbusers = UserFitbit.objects.filter(fitbit_user=fitbit_user) + dates = {'base_date': date, 'period': '1d'} + try: + with transaction.atomic(): + for fbuser in fbusers: + data = utils.get_fitbit_data(fbuser, _type, return_all=True, start_time=start_time, end_time=end_time, + **dates) + resource_path = _type.path().replace('/', '-') + key = resource_path + "-intraday" + if data[key]['datasetType'] != 'minute': + logger.exception("The resource returned is not " + "minute-level data") + raise Reject(sys.exc_info()[1], requeue=False) + intraday = data[key]['dataset'] + logger.info("Date for intraday task: {}".format(date)) + for minute in intraday: + datetime = parser.parse(minute['time'], default=date) + utc_datetime = datetime + timedelta(hours=tz_offset) + utc_datetime = utc_datetime.replace(tzinfo=utc) + value = minute['value'] + # Don't create unnecessary records + if not utils.get_setting('FITAPP_SAVE_INTRADAY_ZERO_VALUES'): + if int(float(value)) == 0: + continue + # Create new record or update existing + tsd, created = TimeSeriesData.objects.get_or_create( + user=fbuser.user, resource_type=_type, date=utc_datetime, + intraday=True) + tsd.value = value + tsd.save() + # Release the lock + except HTTPTooManyRequests: + # We have hit the rate limit for the user, retry when it's reset, + # according to the reply from the failing API call + e = sys.exc_info()[1] + logger.debug('Rate limit reached for user %s, will try again in %s seconds' % + (fitbit_user, e.retry_after_secs)) + raise get_intraday_data.retry(exc=e, countdown=e.retry_after_secs) + except HTTPBadRequest: + # If the resource is elevation or floors, we are just getting this + # error because the data doesn't exist for this user, so we can ignore + # the error + if not ('elevation' in resource or 'floors' in resource): + exc = sys.exc_info()[1] + logger.exception("Exception updating intraday data for user %s: %s" % (fitbit_user, exc)) + raise Reject(exc, requeue=False) + except Exception: + exc = sys.exc_info()[1] + logger.exception("Exception updating data for user %s: %s" % (fitbit_user, exc)) + raise Reject(exc, requeue=False) diff --git a/fitapp/utils.py b/fitapp/utils.py index aacf173..8b0d7f0 100644 --- a/fitapp/utils.py +++ b/fitapp/utils.py @@ -45,7 +45,7 @@ def get_valid_periods(): def get_fitbit_data(fbuser, resource_type, base_date=None, period=None, - end_date=None): + end_date=None, start_time=None, end_time=None): """Creates a Fitbit API instance and retrieves step data for the period. Several exceptions may be thrown: @@ -63,9 +63,16 @@ def get_fitbit_data(fbuser, resource_type, base_date=None, period=None, """ fb = create_fitbit(**fbuser.get_user_data()) resource_path = resource_type.path() - data = fb.time_series(resource_path, user_id=fbuser.fitbit_user, - period=period, base_date=base_date, - end_date=end_date) + + if get_setting('FITAPP_GET_INTRADAY') and resource_type.intraday_support: + # what is the base date? + data = fb.intraday_time_series(resource_path, base_date=base_date, + start_time=start_time, end_time=end_time) + else: + data = fb.time_series(resource_path, user_id=fbuser.fitbit_user, + period=period, base_date=base_date, + end_date=end_date) + return data[resource_path.replace('/', '-')] diff --git a/fitapp/views.py b/fitapp/views.py index 428a984..fdd47fe 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -269,12 +269,13 @@ def update(request): (update['ownerId'], _type.category, _type.resource,), {'date': parser.parse(update['date'])}, countdown=(btw_delay * i)) - # Offset each call by a few seconds so they don't bog down - # the server - get_time_series_data.apply_async( - (update['ownerId'], _type.category, _type.resource,), - {'date': parser.parse(update['date'])}, - countdown=(btw_delay * i)) + else: + # Offset each call by a few seconds so they don't bog down + # the server + get_time_series_data.apply_async( + (update['ownerId'], _type.category, _type.resource,), + {'date': parser.parse(update['date'])}, + countdown=(btw_delay * i)) except (KeyError, ValueError, OverflowError): raise Http404 except ImproperlyConfigured as e: diff --git a/intraday-support-docs.md b/intraday-support-docs.md index 9d2906a..7d8246a 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -56,7 +56,12 @@ Changes to fitapp/migrations when the data needed is intraday enabled, instead of calling get_time_series_data. - Changes to fitapp/tasks.py - - Add currently empty get_intraday_data task. + - Adapt preexisting get_intradat_data task to work with current django-fitbit. + +- Changes to fitapp/utils.py + - Modify get_fitbit_data to use python-fitbit's intraday_time_series function to retrieve data when + a resource category is intraday enabled. + - Add an optional start_time and end_time to get_fitbit_data to use for retrieving intraday data. Planned changes From 5107a675c0667611f4cbc14222839c30cb6ec0d4 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Mon, 23 Jul 2018 15:05:48 -0500 Subject: [PATCH 20/31] Quick fix for upgrading celery from 4.1 to 4.2. --- Pipfile | 2 +- Pipfile.lock | 119 ++++++++++++++++----------------- fitapp/tasks.py | 2 +- fitapp/tests/test_retrieval.py | 21 +++++- fitapp/views.py | 13 ++-- intraday-support-docs.md | 2 +- 6 files changed, 88 insertions(+), 71 deletions(-) diff --git a/Pipfile b/Pipfile index 218f78c..872dd5a 100644 --- a/Pipfile +++ b/Pipfile @@ -5,7 +5,7 @@ name = "pypi" [packages] fitbit = ">=0.3.0" -celery = "<4.2,>=4.1" +celery = "*" simplejson = "<4,>=3.13" mock = "<3,>=2" freezegun = "<0.4,>=0.3.9" diff --git a/Pipfile.lock b/Pipfile.lock index c4f68be..649e576 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "6f216cb394446c9bf77c402fdef17abd64d0e53be4318ed3902fa5a31227574c" + "sha256": "da6426b821f8b973356d16c93ef5ab1f1f38fac949c20bc50c4a6c3dd9ef554b" }, "pipfile-spec": 6, "requires": { @@ -25,18 +25,17 @@ }, "billiard": { "hashes": [ - "sha256:1d7b22bdc47aa52841120fcd22a74ae4fc8c13e9d3935643098184f5788c3ce6", - "sha256:abd9ce008c9a71ccde2c816f8daa36246e92a21e6a799831b887d88277187ecd" + "sha256:ed65448da5877b5558f19d2f7f11f8355ea76b3e63e1c0a6059f47cfae5f1c84" ], - "version": "==3.5.0.3" + "version": "==3.5.0.4" }, "celery": { "hashes": [ - "sha256:6fc4678d1692af97e137b2a9f1c04efd8e7e2fb7134c5c5ad60738cdd927762f", - "sha256:d1f2a3359bdbdfb344edce98b8e891f5fe64f8a11c5a45538ec20ac237c971f5" + "sha256:77dab4677e24dc654d42dfbdfed65fa760455b6bb563a0877ecc35f4cfcfc678", + "sha256:ad7a7411772b80a4d6c64f2f7f723200e39fb66cf614a7fdfab76d345acc7b13" ], "index": "pypi", - "version": "==4.1.1" + "version": "==4.2.1" }, "certifi": { "hashes": [ @@ -54,11 +53,11 @@ }, "django": { "hashes": [ - "sha256:3eb25c99df1523446ec2dc1b00e25eb2ecbdf42c9d8b0b8b32a204a8db9011f8", - "sha256:69ff89fa3c3a8337015478a1a0744f52a9fef5d12c1efa01a01f99bcce9bf10c" + "sha256:97886b8a13bbc33bfeba2ff133035d3eca014e2309dff2b6da0bdfc0b8656613", + "sha256:e900b73beee8977c7b887d90c6c57d68af10066b9dac898e1eaf0f82313de334" ], "index": "pypi", - "version": "==2.0.6" + "version": "==2.0.7" }, "fitbit": { "hashes": [ @@ -106,10 +105,10 @@ }, "pbr": { "hashes": [ - "sha256:3747c6f017f2dc099986c325239661948f9f5176f6880d9fdef164cb664cd665", - "sha256:a9c27eb8f0e24e786e544b2dbaedb729c9d8546342b5a6818d8eda098ad4340d" + "sha256:754e766b4f4bad3aa68cfd532456298da1aa39375da8748392dbae90860d5f18", + "sha256:c6bddbad814f23c7faaf88d8a186e9965243cc6206a23361b73023648e645794" ], - "version": "==4.0.4" + "version": "==4.1.1" }, "python-dateutil": { "hashes": [ @@ -120,10 +119,10 @@ }, "pytz": { "hashes": [ - "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", - "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" + "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", + "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" ], - "version": "==2018.4" + "version": "==2018.5" }, "requests": { "hashes": [ @@ -150,30 +149,30 @@ }, "simplejson": { "hashes": [ - "sha256:1bdd7c7c8c3ece26a251c835e73627a5f825b6ac1d16a68f190c8c29a3a4d4fe", - "sha256:1e651e49b91024e615267fe800ad094c1174800ff06a3b29652f4a656dc51228", - "sha256:2fa4eafab7cb4f900ce7739129cef0da1f25acfa89089540ce6241a15a61df78", - "sha256:346ef48e38d202634ef5f8402d3e043984ef9f504f00b2275807cc8a01ae7d31", - "sha256:446cc58ef7d8a4c5cb336d6893fd6aca1c22800207c18aa72f1496798e2aa6e8", - "sha256:523d3df8dd6a366f8911ffdd9778d2e52d174f1b2ae867c543f9f200ab06595d", - "sha256:60427da697809f2ec0a49b1c9146bf858722bd04fabef77b2b4fca0e883595ef", - "sha256:7418a069e046df6afc9024076a154d35c4df432ad081f2a1cd57661c9dc95b51", - "sha256:839ce3128375c4b6a08b4d20e67befe8fc0d6cf7ed1a1ee6117f035225368de8", - "sha256:8b7644d71f8fb11088660775da0ab09151583939edeb840a53bdf2c5acf3b725", - "sha256:95d19832c666c5942c57e67132138a9332aa84519919a97bd9bbcff1fce7cefd", - "sha256:97502022f2fe5cf78580d5b0889030a62f46080508d32a0992ee7d7d107790b8", - "sha256:a0bdc46d207edaa1db128be6f5b84e415aa033a854ed02d40256fc12d72ce0d7", - "sha256:ad332f65d9551ceffc132d0a683f4ffd12e4bc7538681100190d577ced3473fb", - "sha256:b5263de68cd0891ff4fe8ceb14e6382635ffb1be2ee0c8f7e664681679cd8163", - "sha256:bb68f637d12dccf7ddeece29b4a5f27f15b768dff7f02c198d3d7640f9b8d2dc", - "sha256:d6b38d952a3e90022287998928b12a77471b054455bcded9731dfb8371c47df8", - "sha256:d7e1191ddccabcdc5b300d4469e0fbe69abda6d5e1fba37dad66f8a9913a9994", - "sha256:e22f4bda9177893eb89a6a3cfc5335ae393690407b9b3407e86fe47c2a4adb1d", - "sha256:f00fb192452506454ce7dba6de5a0d5386631e1d6cbc8dcb7e7d4b220bb13c06", - "sha256:fae2430550b625ab2284110f4802ed1a1cae45f96871afbb014ee10f30a37fa3" + "sha256:067a7177ddfa32e1483ba5169ebea1bc2ea27f224853211ca669325648ca5642", + "sha256:2b8cb601d9ba0381499db719ccc9dfbb2fbd16013f5ff096b1a68a4775576a04", + "sha256:2c139daf167b96f21542248f8e0a06596c9b9a7a41c162cc5c9ee9f3833c93cd", + "sha256:2fc546e6af49fb45b93bbe878dea4c48edc34083729c0abd09981fe55bdf7f91", + "sha256:354fa32b02885e6dae925f1b5bbf842c333c1e11ea5453ddd67309dc31fdb40a", + "sha256:37e685986cf6f8144607f90340cff72d36acf654f3653a6c47b84c5c38d00df7", + "sha256:3af610ee72efbe644e19d5eaad575c73fb83026192114e5f6719f4901097fce2", + "sha256:3b919fc9cf508f13b929a9b274c40786036b31ad28657819b3b9ba44ba651f50", + "sha256:3dd289368bbd064974d9a5961101f080e939cbe051e6689a193c99fb6e9ac89b", + "sha256:491de7acc423e871a814500eb2dcea8aa66c4a4b1b4825d18f756cdf58e370cb", + "sha256:495511fe5f10ccf4e3ed4fc0c48318f533654db6c47ecbc970b4ed215c791968", + "sha256:65b41a5cda006cfa7c66eabbcf96aa704a6be2a5856095b9e2fd8c293bad2b46", + "sha256:6c3258ffff58712818a233b9737fe4be943d306c40cf63d14ddc82ba563f483a", + "sha256:75e3f0b12c28945c08f54350d91e624f8dd580ab74fd4f1bbea54bc6b0165610", + "sha256:79b129fe65fdf3765440f7a73edaffc89ae9e7885d4e2adafe6aa37913a00fbb", + "sha256:b1f329139ba647a9548aa05fb95d046b4a677643070dc2afc05fa2e975d09ca5", + "sha256:c206f47cbf9f32b573c9885f0ec813d2622976cf5effcf7e472344bc2e020ac1", + "sha256:d8e238f20bcf70063ee8691d4a72162bcec1f4c38f83c93e6851e72ad545dabb", + "sha256:ee9625fc8ee164902dfbb0ff932b26df112da9f871c32f0f9c1bcf20c350fe2a", + "sha256:fb2530b53c28f0d4d84990e945c2ebb470edb469d63e389bf02ff409012fe7c5", + "sha256:feadb95170e45f439455354904768608e356c5b174ca30b3d11b0e3f24b5c0df" ], "index": "pypi", - "version": "==3.15.0" + "version": "==3.16.0" }, "six": { "hashes": [ @@ -200,10 +199,10 @@ "develop": { "alabaster": { "hashes": [ - "sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732", - "sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0" + "sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456", + "sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7" ], - "version": "==0.7.10" + "version": "==0.7.11" }, "babel": { "hashes": [ @@ -262,11 +261,11 @@ }, "django": { "hashes": [ - "sha256:3eb25c99df1523446ec2dc1b00e25eb2ecbdf42c9d8b0b8b32a204a8db9011f8", - "sha256:69ff89fa3c3a8337015478a1a0744f52a9fef5d12c1efa01a01f99bcce9bf10c" + "sha256:97886b8a13bbc33bfeba2ff133035d3eca014e2309dff2b6da0bdfc0b8656613", + "sha256:e900b73beee8977c7b887d90c6c57d68af10066b9dac898e1eaf0f82313de334" ], "index": "pypi", - "version": "==2.0.6" + "version": "==2.0.7" }, "django-debug-toolbar": { "hashes": [ @@ -328,12 +327,12 @@ }, "pipdeptree": { "hashes": [ - "sha256:362c5877b3b1641671ea05b99d068e86b5cf8b848e8affa8a4af67aa18762d46", - "sha256:65a4aff5d58c4f30d38dc3c1f7b9c6fa4b2e528a2f68a4957196e62f91f3dd62", - "sha256:6683e1779d15828cf42f269e168a3f8559aef28a0c176d5ed85b50cd46634078" + "sha256:013d343fb0305e95f33a81329a30277fcaac45f78ccea90bcfcdb7dbb9d13da2", + "sha256:2cdd29356c9e3a0cab60d1b20571de713abca031a87f4685c31fc0cab3295d19", + "sha256:a2774940d77fa11c1fb275c350080e75c592d1db5ff5679e0be5e566239de83a" ], "index": "pypi", - "version": "==0.12.1" + "version": "==0.13.0" }, "pluggy": { "hashes": [ @@ -345,10 +344,10 @@ }, "py": { "hashes": [ - "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", - "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" + "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", + "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" ], - "version": "==1.5.3" + "version": "==1.5.4" }, "pygments": { "hashes": [ @@ -371,10 +370,10 @@ }, "pytz": { "hashes": [ - "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", - "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" + "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", + "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" ], - "version": "==2018.4" + "version": "==2018.5" }, "requests": { "hashes": [ @@ -399,11 +398,11 @@ }, "sphinx": { "hashes": [ - "sha256:85f7e32c8ef07f4ba5aeca728e0f7717bef0789fba8458b8d9c5c294cad134f3", - "sha256:d45480a229edf70d84ca9fae3784162b1bc75ee47e480ffe04a4b7f21a95d76d" + "sha256:217ad9ece2156ed9f8af12b5d2c82a499ddf2c70a33c5f81864a08d8c67b9efc", + "sha256:a765c6db1e5b62aae857697cd4402a5c1a315a7b0854bbcd0fc8cdc524da5896" ], "index": "pypi", - "version": "==1.7.5" + "version": "==1.7.6" }, "sphinxcontrib-websupport": { "hashes": [ @@ -421,11 +420,11 @@ }, "tox": { "hashes": [ - "sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f", - "sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0" + "sha256:4df108a1fcc93a7ee4ac97e1a3a1fc3d41ddd22445d518976604e2ef05025280", + "sha256:9f0cbcc36e08c2c4ae90d02d3d1f9a62231f974bcbc1df85e8045946d8261059" ], "index": "pypi", - "version": "==3.0.0" + "version": "==3.1.2" }, "urllib3": { "hashes": [ diff --git a/fitapp/tasks.py b/fitapp/tasks.py index 9e7002a..77095af 100644 --- a/fitapp/tasks.py +++ b/fitapp/tasks.py @@ -67,7 +67,7 @@ def get_time_series_data(self, fitbit_user, cat, resource, date=None): try: with transaction.atomic(): - # Block until we have exclusive update access to this UserFitbit, so + # Block until we have exclusive up date access to this UserFitbit, so # that another process cannot step on us when we update tokens fbusers = UserFitbit.objects.select_for_update().filter( fitbit_user=fitbit_user) diff --git a/fitapp/tests/test_retrieval.py b/fitapp/tests/test_retrieval.py index 0e154a8..19c7a97 100644 --- a/fitapp/tests/test_retrieval.py +++ b/fitapp/tests/test_retrieval.py @@ -307,7 +307,7 @@ def side_effect(*args, **kwargs): result = get_time_series_data.apply_async( (self.fbuser.fitbit_user, _type.category, _type.resource,), {'date': parser.parse(self.date)}) - result.get() + # result.get(disable_sync_subtasks=False) # Since celery is in eager mode, we expect a Retry exception first # and then a second task execution that is successful self.assertEqual(get_fitbit_data.call_count, 2) @@ -403,6 +403,25 @@ def test_problem_queueing_task(self): except: assert False, 'Any errors should be captured in the view' +""" + def test_retrieve_intraday_data(self): + print(utils.get_setting('FITAPP_GET_INTRADAY')) + + steps_tsdt = TimeSeriesDataType.objects.get(resource='steps') + steps_tsdt.intraday_support = True + steps_tsdt.save() + + subscription_update_data = json.dumps({'body':[{ + "collectionType": "activities", + "date": "2010-03-01", + "ownerId": "228S74", + "ownerType": "user", + "subscriptionId": "1234" + }]}) + + self.client.post('/update/', subscription_update_data) +""" + class RetrievalViewTestBase(object): """Base methods for the get_steps view.""" diff --git a/fitapp/views.py b/fitapp/views.py index fdd47fe..428a984 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -269,13 +269,12 @@ def update(request): (update['ownerId'], _type.category, _type.resource,), {'date': parser.parse(update['date'])}, countdown=(btw_delay * i)) - else: - # Offset each call by a few seconds so they don't bog down - # the server - get_time_series_data.apply_async( - (update['ownerId'], _type.category, _type.resource,), - {'date': parser.parse(update['date'])}, - countdown=(btw_delay * i)) + # Offset each call by a few seconds so they don't bog down + # the server + get_time_series_data.apply_async( + (update['ownerId'], _type.category, _type.resource,), + {'date': parser.parse(update['date'])}, + countdown=(btw_delay * i)) except (KeyError, ValueError, OverflowError): raise Http404 except ImproperlyConfigured as e: diff --git a/intraday-support-docs.md b/intraday-support-docs.md index 26e7f0f..1142f19 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -57,7 +57,7 @@ Changes to fitapp/migrations when the data needed is intraday enabled, instead of calling get_time_series_data. - Changes to fitapp/tasks.py - - Adapt preexisting get_intradat_data task to work with current django-fitbit. + - Adapt preexisting get_intraday_data task to work with current django-fitbit. - Changes to fitapp/utils.py - Modify get_fitbit_data to use python-fitbit's intraday_time_series function to retrieve data when From 72d1ac502dbdc8345fb761c2f2d1e4f722060f66 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Mon, 23 Jul 2018 15:43:50 -0500 Subject: [PATCH 21/31] Update celery version in requirements. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 3122859..24b63ff 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ fitbit>=0.3.0 -celery>=4.1,<4.2 +celery simplejson>=3.13,<4 six \ No newline at end of file From 3e79f7a9e6080607509e19e14529a23989bdd1c2 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Fri, 27 Jul 2018 17:01:41 -0500 Subject: [PATCH 22/31] Add swappable user model. --- fitapp/models.py | 2 +- intraday-support-docs.md | 4 ++++ test_settings.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/fitapp/models.py b/fitapp/models.py index 785f36d..ddb888f 100644 --- a/fitapp/models.py +++ b/fitapp/models.py @@ -2,7 +2,7 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible -UserModel = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') +UserModel = getattr(settings, 'FITAPP_USER_MODEL', 'auth.User') @python_2_unicode_compatible diff --git a/intraday-support-docs.md b/intraday-support-docs.md index 1142f19..b709380 100644 --- a/intraday-support-docs.md +++ b/intraday-support-docs.md @@ -33,6 +33,7 @@ as intraday-compatible. - VERY IMPORTANT: There are not currently checks to see if the given Fitbit app has authorization for a certain category of intraday data. Be sure your app has authorization before attempting to retrieve it. - Intraday TimeSeriesData instances will be marked with intraday = True. +- There must be a FITAPP_USER_MODEL setting set to whatever model you want UserFitbits and TimeSeriesData associated with. What was changed @@ -64,6 +65,9 @@ Changes to fitapp/migrations a resource category is intraday enabled. - Add an optional start_time and end_time to get_fitbit_data to use for retrieving intraday data. +- Changes to enable swappable model to which UserFitbits and data is associated + - Create new setting, FITAPP_USER_MODEL, which must be set to the model to associate UserFitbits and TimeSeriesData to. + - Set this setting to a default in test_settings.py Planned changes diff --git a/test_settings.py b/test_settings.py index a41e667..6268a42 100644 --- a/test_settings.py +++ b/test_settings.py @@ -1,5 +1,6 @@ import sys +from django.conf import settings DATABASES = { 'default': { @@ -22,6 +23,7 @@ FITAPP_SUBSCRIBE = True FITAPP_SUBSCRIBER_ID = 1 FITAPP_GET_INTRADAY = True +FITAPP_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') LOGGING = { 'version': 1, From 32fa2640917f802bcd35464c16fe794efac5c424 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Sat, 18 Aug 2018 16:44:07 -0500 Subject: [PATCH 23/31] Update views.complete to account for swappable user model. --- fitapp/views.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fitapp/views.py b/fitapp/views.py index 428a984..f68b796 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -67,9 +67,15 @@ def complete(request): If :ref:`FITAPP_SUBSCRIBE` is set to True, add a subscription to user data at this time. + Requires the pk of the user or model that will be associated with fitapp + data to be inserted into request.session['fb_user_id'] prior to calling + the view. + URL name: `fitbit-complete` """ + user_model = UserFitbit.user.field.rel.to + try: code = request.GET['code'] except KeyError: @@ -80,14 +86,16 @@ def complete(request): try: token = fb.client.fetch_access_token(code, callback_uri) access_token = token['access_token'] + fb_user_id = int(request.session.get('fb_user_id')) + user = user_model.objects.get(pk=fb_user_id) fitbit_user = token['user_id'] except KeyError: return redirect(reverse('fitbit-error')) if UserFitbit.objects.filter(fitbit_user=fitbit_user).exists(): + current_fbuser = UserFitbit.objects.filter(fitbit_user=fitbit_user).first() return redirect(reverse('fitbit-error')) - user = request.user fbuser, _ = UserFitbit.objects.update_or_create(user=user, defaults={ 'fitbit_user': fitbit_user, 'access_token': access_token, @@ -124,7 +132,7 @@ def complete(request): tsdts = tsdts.filter(resource__in=res) # Sort as specified in FITAPP_SUBSCRIPTIONS tsdts = sorted(tsdts, key=lambda tsdt: ( - cats.index(tsdt.category) + res.index(tsdt.resource) + cats.index(tsdt.category) + res.index(tsdt.resource) )) # Create tasks for all data in all data types From bb8b4ca8a4468264ff34067e19dea72b086b46af Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Sat, 18 Aug 2018 16:58:28 -0500 Subject: [PATCH 24/31] Modify views.complete. --- fitapp/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fitapp/views.py b/fitapp/views.py index f68b796..acdf2fd 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -1,6 +1,8 @@ import simplejson as json from dateutil import parser from dateutil.relativedelta import relativedelta + +from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.auth.signals import user_logged_in from django.core.exceptions import ImproperlyConfigured @@ -74,7 +76,7 @@ def complete(request): URL name: `fitbit-complete` """ - user_model = UserFitbit.user.field.rel.to + user_model = getattr(settings, 'FITAPP_USER_MODEL', 'auth.User') try: code = request.GET['code'] From b2e01520b09a2d9ff973dc341a3bf46fdb3cd7e0 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Sat, 18 Aug 2018 17:04:06 -0500 Subject: [PATCH 25/31] Modify views.complete. --- fitapp/views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fitapp/views.py b/fitapp/views.py index acdf2fd..7696072 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -1,8 +1,6 @@ import simplejson as json from dateutil import parser from dateutil.relativedelta import relativedelta - -from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.auth.signals import user_logged_in from django.core.exceptions import ImproperlyConfigured @@ -76,7 +74,7 @@ def complete(request): URL name: `fitbit-complete` """ - user_model = getattr(settings, 'FITAPP_USER_MODEL', 'auth.User') + user_model = UserFitbit.user.field.remote_field.model try: code = request.GET['code'] From f150038207bb216f4e13dfd73bd4e36a6698ae61 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Sat, 18 Aug 2018 17:06:01 -0500 Subject: [PATCH 26/31] Experimentally remove login_required decorator from complete view. --- fitapp/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fitapp/views.py b/fitapp/views.py index 7696072..c295135 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -50,7 +50,6 @@ def login(request): return redirect(token_url) -@login_required def complete(request): """ After the user authorizes us, Fitbit sends a callback to this URL to From dc83415929942b039553ecc7bc3457c20b4adf74 Mon Sep 17 00:00:00 2001 From: Madeline Silva Date: Wed, 22 Aug 2018 16:09:28 -0500 Subject: [PATCH 27/31] Changes needed to make data pulling work correctly. --- fitapp/tasks.py | 30 ++++++++++++++++++------------ fitapp/utils.py | 5 ++--- fitapp/views.py | 15 ++++++++------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/fitapp/tasks.py b/fitapp/tasks.py index 77095af..51dc2fd 100644 --- a/fitapp/tasks.py +++ b/fitapp/tasks.py @@ -1,11 +1,15 @@ import logging import random +import sys +import datetime +import time from celery import shared_task from celery.exceptions import Ignore, Reject from dateutil import parser from django.core.cache import cache from django.db import transaction +from django.utils.timezone import utc from fitbit.exceptions import HTTPBadRequest, HTTPTooManyRequests from . import utils @@ -57,13 +61,13 @@ def get_time_series_data(self, fitbit_user, cat, resource, date=None): resource, cat)) raise Reject(e, requeue=False) - # Create a lock so we don't try to run the same task multiple times - sdat = date.strftime('%Y-%m-%d') if date else 'ALL' - lock_id = '{0}-lock-{1}-{2}-{3}'.format(__name__, fitbit_user, _type, sdat) - if not cache.add(lock_id, 'true', LOCK_EXPIRE): - logger.debug('Already retrieving %s data for date %s, user %s' % ( - _type, fitbit_user, sdat)) - raise Ignore() + # # Create a lock so we don't try to run the same task multiple times + # sdat = date.strftime('%Y-%m-%d') if date else 'ALL' + # lock_id = '{0}-lock-{1}-{2}-{3}'.format(__name__, fitbit_user, _type, sdat) + # if not cache.add(lock_id, 'true', LOCK_EXPIRE): + # logger.debug('Already retrieving %s data for date %s, user %s' % ( + # _type, fitbit_user, sdat)) + # raise Ignore() try: with transaction.atomic(): @@ -108,7 +112,8 @@ def get_time_series_data(self, fitbit_user, cat, resource, date=None): raise Reject(e, requeue=False) -def get_intraday_data(fitbit_user, cat, resource, date, tz_offset, start_time=None, end_time=None): +@shared_task(bind=True) +def get_intraday_data(self, fitbit_user, cat, resource, date, tz_offset, start_time=None, end_time=None): """ Get the user's intraday data for a specified date, convert to UTC prior to saving. @@ -128,14 +133,15 @@ def get_intraday_data(fitbit_user, cat, resource, date, tz_offset, start_time=No raise Reject(sys.exc_info()[1], requeue=False) # Create a lock so we don't try to run the same task multiple times - sdat = date.strftime('%Y-%m-%d') + # sdat = date.strftime('%Y-%m-%d') fbusers = UserFitbit.objects.filter(fitbit_user=fitbit_user) dates = {'base_date': date, 'period': '1d'} try: with transaction.atomic(): for fbuser in fbusers: - data = utils.get_fitbit_data(fbuser, _type, return_all=True, start_time=start_time, end_time=end_time, + data = utils.get_fitbit_data(fbuser, _type, start_time=start_time, + end_time=end_time, **dates) resource_path = _type.path().replace('/', '-') key = resource_path + "-intraday" @@ -146,8 +152,8 @@ def get_intraday_data(fitbit_user, cat, resource, date, tz_offset, start_time=No intraday = data[key]['dataset'] logger.info("Date for intraday task: {}".format(date)) for minute in intraday: - datetime = parser.parse(minute['time'], default=date) - utc_datetime = datetime + timedelta(hours=tz_offset) + date_time = parser.parse(date[:10] + ' ' + minute['time']) + utc_datetime = date_time + datetime.timedelta(hours=tz_offset) utc_datetime = utc_datetime.replace(tzinfo=utc) value = minute['value'] # Don't create unnecessary records diff --git a/fitapp/utils.py b/fitapp/utils.py index 8b0d7f0..11f2111 100644 --- a/fitapp/utils.py +++ b/fitapp/utils.py @@ -65,15 +65,14 @@ def get_fitbit_data(fbuser, resource_type, base_date=None, period=None, resource_path = resource_type.path() if get_setting('FITAPP_GET_INTRADAY') and resource_type.intraday_support: - # what is the base date? + base_date = base_date[:10] data = fb.intraday_time_series(resource_path, base_date=base_date, start_time=start_time, end_time=end_time) else: data = fb.time_series(resource_path, user_id=fbuser.fitbit_user, period=period, base_date=base_date, end_date=end_date) - - return data[resource_path.replace('/', '-')] + return data def get_setting(name, use_defaults=True): diff --git a/fitapp/views.py b/fitapp/views.py index c295135..711fc75 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -272,16 +272,17 @@ def update(request): ) for i, _type in enumerate(tsdts): if utils.get_setting('FITAPP_GET_INTRADAY') and _type.intraday_support: - get_intraday_data.apply.async( - (update['ownerId'], _type.category, _type.resource,), - {'date': parser.parse(update['date'])}, + date = parser.parse(update['date']) + get_intraday_data.apply_async( + (update['ownerId'], _type.category, _type.resource, date, 0), countdown=(btw_delay * i)) # Offset each call by a few seconds so they don't bog down # the server - get_time_series_data.apply_async( - (update['ownerId'], _type.category, _type.resource,), - {'date': parser.parse(update['date'])}, - countdown=(btw_delay * i)) + else: + get_time_series_data.apply_async( + (update['ownerId'], _type.category, _type.resource,), + {'date': parser.parse(update['date'])}, + countdown=(btw_delay * i)) except (KeyError, ValueError, OverflowError): raise Http404 except ImproperlyConfigured as e: From 268fc15690406b4649c0157fa24584a0b3ca500c Mon Sep 17 00:00:00 2001 From: madsilva Date: Mon, 1 Oct 2018 17:21:43 -0500 Subject: [PATCH 28/31] Remove login_required decorator from views. --- fitapp/views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fitapp/views.py b/fitapp/views.py index 711fc75..bc41bad 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -1,7 +1,6 @@ import simplejson as json from dateutil import parser from dateutil.relativedelta import relativedelta -from django.contrib.auth.decorators import login_required from django.contrib.auth.signals import user_logged_in from django.core.exceptions import ImproperlyConfigured from django.dispatch import receiver @@ -21,7 +20,6 @@ from .tasks import get_time_series_data, subscribe, unsubscribe, get_intraday_data -@login_required def login(request): """ Begins the OAuth authentication process by obtaining a Request Token from @@ -162,7 +160,6 @@ def create_fitbit_session(sender, request, user, **kwargs): pass -@login_required def error(request): """ The user is redirected to this view if we encounter an error acquiring @@ -188,7 +185,6 @@ def error(request): return render(request, utils.get_setting('FITAPP_ERROR_TEMPLATE'), {}) -@login_required def logout(request): """Forget this user's Fitbit credentials. From 0d4cc4d90ab027aa9287cd6de00f74e1b0d432d3 Mon Sep 17 00:00:00 2001 From: Justin Perez Date: Tue, 2 Oct 2018 11:29:50 -0500 Subject: [PATCH 29/31] Add swappable user model to migration --- fitapp/migrations/0001_initial.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fitapp/migrations/0001_initial.py b/fitapp/migrations/0001_initial.py index edeea24..402868f 100644 --- a/fitapp/migrations/0001_initial.py +++ b/fitapp/migrations/0001_initial.py @@ -6,6 +6,9 @@ class Migration(migrations.Migration): + + UserModel = getattr(settings, 'FITAPP_USER_MODEL', 'auth.User') + dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] From cd93cbea63c7a137c92f711dc930cf4ae0164ece Mon Sep 17 00:00:00 2001 From: Justin Perez Date: Tue, 2 Oct 2018 16:16:01 -0500 Subject: [PATCH 30/31] Fix migrations to use correct user model --- fitapp/migrations/0001_initial.py | 6 +++--- fitapp/migrations/0006_help_text.py | 7 +++++-- fitapp/migrations/0009_auto_20180110_1605.py | 6 ++++-- fitapp/migrations/0010_auto_20180710_1846.py | 4 ++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/fitapp/migrations/0001_initial.py b/fitapp/migrations/0001_initial.py index 402868f..d8bdfaf 100644 --- a/fitapp/migrations/0001_initial.py +++ b/fitapp/migrations/0001_initial.py @@ -10,7 +10,7 @@ class Migration(migrations.Migration): UserModel = getattr(settings, 'FITAPP_USER_MODEL', 'auth.User') dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + migrations.swappable_dependency(UserModel), ] operations = [ @@ -53,7 +53,7 @@ class Migration(migrations.Migration): ('fitbit_user', models.CharField(unique=True, max_length=32)), ('auth_token', models.TextField()), ('auth_secret', models.TextField()), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, + ('user', models.OneToOneField(to=UserModel, on_delete=models.CASCADE)), ], options={ @@ -74,7 +74,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='timeseriesdata', name='user', - field=models.ForeignKey(to=settings.AUTH_USER_MODEL, + field=models.ForeignKey(to=UserModel, on_delete=models.CASCADE), preserve_default=True, ), diff --git a/fitapp/migrations/0006_help_text.py b/fitapp/migrations/0006_help_text.py index a424b7f..f8ef04d 100644 --- a/fitapp/migrations/0006_help_text.py +++ b/fitapp/migrations/0006_help_text.py @@ -6,8 +6,11 @@ class Migration(migrations.Migration): + UserModel = getattr(settings, 'FITAPP_USER_MODEL', 'auth.User') + dependencies = [ ('fitapp', '0005_upgrade_oauth1_tokens_to_oauth2'), + migrations.swappable_dependency(UserModel), ] operations = [ @@ -27,7 +30,7 @@ class Migration(migrations.Migration): model_name='timeseriesdata', name='user', field=models.ForeignKey(help_text=b"The data's user", - to=settings.AUTH_USER_MODEL, + to=UserModel, on_delete=models.CASCADE ), ), @@ -75,7 +78,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='userfitbit', name='user', - field=models.OneToOneField(to=settings.AUTH_USER_MODEL, help_text=b'The user', + field=models.OneToOneField(to=UserModel, help_text=b'The user', on_delete=models.CASCADE), ), ] diff --git a/fitapp/migrations/0009_auto_20180110_1605.py b/fitapp/migrations/0009_auto_20180110_1605.py index 869c99a..dee1acc 100644 --- a/fitapp/migrations/0009_auto_20180110_1605.py +++ b/fitapp/migrations/0009_auto_20180110_1605.py @@ -6,8 +6,10 @@ class Migration(migrations.Migration): + UserModel = getattr(settings, 'FITAPP_USER_MODEL', 'auth.User') dependencies = [ ('fitapp', '0008_remove_userfitbit_auth_secret'), + migrations.swappable_dependency(UserModel), ] operations = [ @@ -28,7 +30,7 @@ class Migration(migrations.Migration): name='user', field=models.ForeignKey(help_text="The data's user", on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL), + to=UserModel), ), migrations.AlterField( model_name='timeseriesdata', @@ -78,6 +80,6 @@ class Migration(migrations.Migration): name='user', field=models.OneToOneField(help_text='The user', on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL), + to=UserModel), ), ] diff --git a/fitapp/migrations/0010_auto_20180710_1846.py b/fitapp/migrations/0010_auto_20180710_1846.py index 15c3f29..4e78c23 100644 --- a/fitapp/migrations/0010_auto_20180710_1846.py +++ b/fitapp/migrations/0010_auto_20180710_1846.py @@ -5,9 +5,9 @@ class Migration(migrations.Migration): - + UserModel = getattr(settings, 'FITAPP_USER_MODEL', 'auth.User') dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + migrations.swappable_dependency(UserModel), ('fitapp', '0009_auto_20180110_1605'), ] From aa17ee5dacbbf4ad1edea85f480829185e6f39f9 Mon Sep 17 00:00:00 2001 From: Justin Perez Date: Thu, 4 Oct 2018 14:46:28 -0500 Subject: [PATCH 31/31] Just update existing UserFitbit, dont throw error --- fitapp/views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fitapp/views.py b/fitapp/views.py index bc41bad..c398325 100644 --- a/fitapp/views.py +++ b/fitapp/views.py @@ -89,10 +89,6 @@ def complete(request): except KeyError: return redirect(reverse('fitbit-error')) - if UserFitbit.objects.filter(fitbit_user=fitbit_user).exists(): - current_fbuser = UserFitbit.objects.filter(fitbit_user=fitbit_user).first() - return redirect(reverse('fitbit-error')) - fbuser, _ = UserFitbit.objects.update_or_create(user=user, defaults={ 'fitbit_user': fitbit_user, 'access_token': access_token,