merge experimental #1
@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -108,7 +108,8 @@ def register_continue(request):
|
||||
instance.save()
|
||||
request.session.flush()
|
||||
|
||||
# TODO sync withings health data
|
||||
withings_api_account.update_records()
|
||||
|
||||
# TODO redirect user to some other page and ask them to log in
|
||||
return redirect('dashboard')
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.2.3 on 2023-07-27 14:35
|
||||
# Generated by Django 4.2.3 on 2023-07-29 18:28
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.2.3 on 2023-07-27 14:35
|
||||
# Generated by Django 4.2.3 on 2023-07-29 18:28
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
@ -20,7 +20,7 @@ class Migration(migrations.Migration):
|
||||
name='BloodPressureRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recorded', models.DateTimeField(validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('value_systolic_mmhg', models.PositiveIntegerField(validators=[medwings.validators.BloodPressureRecordValidator.value_systolic_mmhg], verbose_name='Systolic Blood Pressure (mmhg)')),
|
||||
('value_diastolic_mmhg', models.PositiveIntegerField(validators=[medwings.validators.BloodPressureRecordValidator.value_diastolic_mmhg], verbose_name='Diastolic Blood Pressure (mmhg)')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
@ -30,8 +30,8 @@ class Migration(migrations.Migration):
|
||||
name='BodyTempRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recorded', models.DateTimeField(validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('value_celsius', models.PositiveIntegerField(validators=[medwings.validators.BodyTempRecordValidator.value_celsius], verbose_name='Body Temperature (°C)')),
|
||||
('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('value_celsius', models.DecimalField(decimal_places=2, max_digits=5, unique=True, validators=[medwings.validators.BodyTempRecordValidator.value_celsius], verbose_name='Body Temperature (°C)')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
@ -39,7 +39,7 @@ class Migration(migrations.Migration):
|
||||
name='HeartRateRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recorded', models.DateTimeField(validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('value_bpm', models.PositiveIntegerField(validators=[medwings.validators.HeartRateRecordValidator.value_bpm], verbose_name='Heart Rate (bpm)')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
@ -56,7 +56,7 @@ class Migration(migrations.Migration):
|
||||
name='Spo2LevelRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recorded', models.DateTimeField(validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('value_percent', models.PositiveIntegerField(validators=[medwings.validators.Spo2LevelRecordValidator.value_percent], verbose_name='SPO2 (%)')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
@ -65,7 +65,7 @@ class Migration(migrations.Migration):
|
||||
name='RespirationScoreRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recorded', models.DateTimeField(validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was taken')),
|
||||
('value_severity', models.PositiveIntegerField(choices=[(0, 'No shortness of breath'), (1, 'A little shortness of breath'), (2, 'Severe shortness of breath')], validators=[medwings.validators.RespirationScoreRecordValidator.value_severity], verbose_name='Shortness Of Breath Severity')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
@ -74,7 +74,7 @@ class Migration(migrations.Migration):
|
||||
name='MewsRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recorded', models.DateTimeField(validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was calculated')),
|
||||
('recorded', models.DateTimeField(unique=True, validators=[medwings.validators.AbstractRecordValidator.recorded], verbose_name='Time at which measurement was calculated')),
|
||||
('value_n', models.PositiveIntegerField(validators=[medwings.validators.MewsRecordValidator.value_n], verbose_name='Modified Early Warning Score')),
|
||||
('blood_pressure_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='medwings.bloodpressurerecord')),
|
||||
('body_temp_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='medwings.bodytemprecord')),
|
||||
|
@ -19,23 +19,29 @@ class Profile(models.Model):
|
||||
|
||||
class BloodPressureRecord(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
recorded = models.DateTimeField(validators=[validators.BloodPressureRecordValidator.recorded], verbose_name="Time at which measurement was taken")
|
||||
recorded = models.DateTimeField(validators=[validators.BloodPressureRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken")
|
||||
value_systolic_mmhg = models.PositiveIntegerField(validators=[validators.BloodPressureRecordValidator.value_systolic_mmhg], verbose_name="Systolic Blood Pressure (mmhg)")
|
||||
value_diastolic_mmhg = models.PositiveIntegerField(validators=[validators.BloodPressureRecordValidator.value_diastolic_mmhg], verbose_name="Diastolic Blood Pressure (mmhg)")
|
||||
|
||||
|
||||
class BodyTempRecord(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
recorded = models.DateTimeField(validators=[validators.BodyTempRecordValidator.recorded], verbose_name="Time at which measurement was taken")
|
||||
value_celsius = models.PositiveIntegerField(validators=[validators.BodyTempRecordValidator.value_celsius], verbose_name="Body Temperature (\u00B0C)")
|
||||
recorded = models.DateTimeField(validators=[validators.BodyTempRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken")
|
||||
value_celsius = models.DecimalField(max_digits=5, decimal_places=2, validators=[validators.BodyTempRecordValidator.value_celsius], unique=True, verbose_name="Body Temperature (\u00B0C)")
|
||||
|
||||
|
||||
class HeartRateRecord(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
recorded = models.DateTimeField(validators=[validators.HeartRateRecordValidator.recorded], verbose_name="Time at which measurement was taken")
|
||||
recorded = models.DateTimeField(validators=[validators.HeartRateRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken")
|
||||
value_bpm = models.PositiveIntegerField(validators=[validators.HeartRateRecordValidator.value_bpm], verbose_name="Heart Rate (bpm)")
|
||||
|
||||
|
||||
class Spo2LevelRecord(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
recorded = models.DateTimeField(validators=[validators.Spo2LevelRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken")
|
||||
value_percent = models.PositiveIntegerField(validators=[validators.Spo2LevelRecordValidator.value_percent], verbose_name="SPO2 (\u0025)")
|
||||
|
||||
|
||||
class RespirationScoreRecord(models.Model):
|
||||
SEVERITY_NONE = 0
|
||||
SEVERITY_LOW = 1
|
||||
@ -47,19 +53,13 @@ class RespirationScoreRecord(models.Model):
|
||||
]
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
recorded = models.DateTimeField(validators=[validators.RespirationScoreRecordValidator.recorded], verbose_name="Time at which measurement was taken")
|
||||
recorded = models.DateTimeField(validators=[validators.RespirationScoreRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken")
|
||||
value_severity = models.PositiveIntegerField(choices=SEVERITY_CHOICES, validators=[validators.RespirationScoreRecordValidator.value_severity], verbose_name="Shortness Of Breath Severity")
|
||||
|
||||
|
||||
class Spo2LevelRecord(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
recorded = models.DateTimeField(validators=[validators.Spo2LevelRecordValidator.recorded], verbose_name="Time at which measurement was taken")
|
||||
value_percent = models.PositiveIntegerField(validators=[validators.Spo2LevelRecordValidator.value_percent], verbose_name="SPO2 (\u0025)")
|
||||
|
||||
|
||||
class MewsRecord(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
recorded = models.DateTimeField(validators=[validators.MewsRecordValidator.recorded], verbose_name="Time at which measurement was calculated")
|
||||
recorded = models.DateTimeField(validators=[validators.MewsRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was calculated")
|
||||
value_n = models.PositiveIntegerField(validators=[validators.MewsRecordValidator.value_n], verbose_name="Modified Early Warning Score")
|
||||
|
||||
blood_pressure_record = models.ForeignKey(BloodPressureRecord, on_delete=models.CASCADE)
|
||||
|
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -5,6 +5,7 @@ Django==4.2.3
|
||||
idna==3.4
|
||||
psycopg==3.1.9
|
||||
psycopg-binary==3.1.9
|
||||
pytz==2023.3
|
||||
requests==2.31.0
|
||||
sqlparse==0.4.4
|
||||
typing_extensions==4.7.1
|
||||
|
210
app/withings/README.md
Normal file
210
app/withings/README.md
Normal file
@ -0,0 +1,210 @@
|
||||
# Withings API
|
||||
|
||||
## Token expiry
|
||||
|
||||
When the access token expires, HTTP status `200 OK` is returned, but the response body is as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 401,
|
||||
"body": {},
|
||||
"error": "XRequestID: Not provided invalid_token: The access token provided is invalid"
|
||||
}
|
||||
```
|
||||
|
||||
## Fetching health data
|
||||
|
||||
Health records can be fetched via GET request as follows:
|
||||
|
||||
```http
|
||||
https://wbsapi.withings.net/measure?action=getmeas&meastypes=9,10,54,71,11
|
||||
```
|
||||
|
||||
The type of vitals measurement is mapped as follows:
|
||||
|
||||
| Code | Type | Unit |
|
||||
|------|--------------------------|------|
|
||||
| 9 | Diastolic Blood Pressure | mmHg |
|
||||
| 10 | Systolic Blood Pressure | mmHg |
|
||||
| 11 | Heart Rate | bpm |
|
||||
| 54 | SP02 | % |
|
||||
| 71 | Body Temperature | °C |
|
||||
|
||||
Note the `unit`-field in the response.
|
||||
For body temperature, the `unit`-field has the value `-3`.
|
||||
This means that to get the body temperature in °C, you must multiply the `value` by `10^(-3)`.
|
||||
|
||||
The time of measurement can be parsed from the `measuregrps`'s `date` field.
|
||||
|
||||
A successful response looks like so:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 0,
|
||||
"body": {
|
||||
"updatetime": 1690491663,
|
||||
"timezone": "Europe/Berlin",
|
||||
"measuregrps": [
|
||||
{
|
||||
"grpid": 4716596696,
|
||||
"attrib": 0,
|
||||
"date": 1690491576,
|
||||
"created": 1690491663,
|
||||
"modified": 1690491663,
|
||||
"category": 1,
|
||||
"deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956",
|
||||
"hash_deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956",
|
||||
"measures": [
|
||||
{
|
||||
"value": 89,
|
||||
"type": 9,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
},
|
||||
{
|
||||
"value": 109,
|
||||
"type": 10,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
},
|
||||
{
|
||||
"value": 88,
|
||||
"type": 11,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
}
|
||||
],
|
||||
"modelid": 44,
|
||||
"model": "BPM Core",
|
||||
"comment": null
|
||||
},
|
||||
{
|
||||
"grpid": 4716596681,
|
||||
"attrib": 0,
|
||||
"date": 1690491236,
|
||||
"created": 1690491662,
|
||||
"modified": 1690491662,
|
||||
"category": 1,
|
||||
"deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956",
|
||||
"hash_deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956",
|
||||
"measures": [
|
||||
{
|
||||
"value": 65,
|
||||
"type": 9,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
},
|
||||
{
|
||||
"value": 92,
|
||||
"type": 10,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
},
|
||||
{
|
||||
"value": 88,
|
||||
"type": 11,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
}
|
||||
],
|
||||
"modelid": 44,
|
||||
"model": "BPM Core",
|
||||
"comment": null
|
||||
},
|
||||
{
|
||||
"grpid": 4712963495,
|
||||
"attrib": 0,
|
||||
"date": 1690375238,
|
||||
"created": 1690375243,
|
||||
"modified": 1690375243,
|
||||
"category": 1,
|
||||
"deviceid": "dbf7f61809d5fb350a16a50f6af6e826f0746082",
|
||||
"hash_deviceid": "dbf7f61809d5fb350a16a50f6af6e826f0746082",
|
||||
"measures": [
|
||||
{
|
||||
"value": 99,
|
||||
"type": 54,
|
||||
"unit": 0,
|
||||
"algo": 33619971,
|
||||
"fm": 3,
|
||||
"apppfmid": 9,
|
||||
"appliver": 2741,
|
||||
"algo_params": {
|
||||
"1": 0,
|
||||
"2": 15
|
||||
}
|
||||
}
|
||||
],
|
||||
"modelid": null,
|
||||
"model": null,
|
||||
"comment": null,
|
||||
"is_inconclusive": false
|
||||
},
|
||||
{
|
||||
"grpid": 4712927310,
|
||||
"attrib": 1,
|
||||
"date": 1690374434,
|
||||
"created": 1690374456,
|
||||
"modified": 1690374486,
|
||||
"category": 1,
|
||||
"deviceid": "1d453daf947378fac40677e7a085eea73750b061",
|
||||
"hash_deviceid": "1d453daf947378fac40677e7a085eea73750b061",
|
||||
"measures": [
|
||||
{
|
||||
"value": 37370,
|
||||
"type": 71,
|
||||
"unit": -3,
|
||||
"algo": 0,
|
||||
"fm": 0
|
||||
}
|
||||
],
|
||||
"modelid": null,
|
||||
"model": null,
|
||||
"comment": null
|
||||
},
|
||||
{
|
||||
"grpid": 4712911433,
|
||||
"attrib": 0,
|
||||
"date": 1690373994,
|
||||
"created": 1690374078,
|
||||
"modified": 1690374078,
|
||||
"category": 1,
|
||||
"deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956",
|
||||
"hash_deviceid": "c405eb8e0e053f6601e151c7e43c04e29aad6956",
|
||||
"measures": [
|
||||
{
|
||||
"value": 88,
|
||||
"type": 9,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
},
|
||||
{
|
||||
"value": 124,
|
||||
"type": 10,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
},
|
||||
{
|
||||
"value": 70,
|
||||
"type": 11,
|
||||
"unit": 0,
|
||||
"algo": 0,
|
||||
"fm": 3
|
||||
}
|
||||
],
|
||||
"modelid": null,
|
||||
"model": null,
|
||||
"comment": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -1,11 +1,15 @@
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from random import randint
|
||||
|
||||
import requests
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from medwings import models as mm
|
||||
|
||||
def fetch_initial_tokens(authorization_code, redirect_uri):
|
||||
data = {
|
||||
'action': 'requesttoken',
|
||||
@ -49,3 +53,64 @@ def save_tokens_to_session(request, response_data):
|
||||
now = timezone.now()
|
||||
request.session['withings_access_token_expiry'] = (now + timedelta(seconds=response_data['body']['expires_in'])).isoformat()
|
||||
request.session['withings_refresh_token_expiry'] = (now + timedelta(days=365)).isoformat()
|
||||
|
||||
|
||||
def parse_getmeas_response(response_body: dict, user: User) -> list:
|
||||
body = response_body['body']
|
||||
records = []
|
||||
|
||||
timezone = pytz.timezone(body['timezone'])
|
||||
|
||||
for measure_group in body['measuregrps']:
|
||||
recorded = timezone.localize(datetime.fromtimestamp(measure_group['date']))
|
||||
|
||||
blood_pressure_systolic_value = None
|
||||
blood_pressure_diastolic_value = None
|
||||
body_temperature_value = None
|
||||
heart_rate_value = None
|
||||
spo2_level_value = None
|
||||
for measure in measure_group['measures']:
|
||||
measure_type = measure['type']
|
||||
measure_value = measure['value']
|
||||
measure_unit = measure['unit']
|
||||
|
||||
measure_value_adjusted = measure_value * (10 ** measure_unit)
|
||||
|
||||
if measure_type == 9:
|
||||
blood_pressure_diastolic_value = measure_value_adjusted
|
||||
elif measure_type == 10:
|
||||
blood_pressure_systolic_value = measure_value_adjusted
|
||||
elif measure_type == 11:
|
||||
heart_rate_value = measure_value_adjusted
|
||||
elif measure_type == 54:
|
||||
spo2_level_value = measure_value_adjusted
|
||||
elif measure_type == 71:
|
||||
body_temperature_value = measure_value_adjusted
|
||||
|
||||
if blood_pressure_systolic_value and blood_pressure_diastolic_value:
|
||||
records.append(mm.BloodPressureRecord(
|
||||
user=user,
|
||||
recorded=recorded,
|
||||
value_systolic_mmhg=blood_pressure_systolic_value,
|
||||
value_diastolic_mmhg=blood_pressure_diastolic_value
|
||||
))
|
||||
if body_temperature_value:
|
||||
records.append(mm.BodyTempRecord(
|
||||
user=user,
|
||||
recorded=recorded,
|
||||
value_celsius=body_temperature_value
|
||||
))
|
||||
if heart_rate_value:
|
||||
records.append(mm.HeartRateRecord(
|
||||
user=user,
|
||||
recorded=recorded,
|
||||
value_bpm=heart_rate_value
|
||||
))
|
||||
if spo2_level_value:
|
||||
records.append(mm.Spo2LevelRecord(
|
||||
user=user,
|
||||
recorded=recorded,
|
||||
value_percent=spo2_level_value
|
||||
))
|
||||
|
||||
return records
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.2.3 on 2023-07-27 14:35
|
||||
# Generated by Django 4.2.3 on 2023-07-29 18:35
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
@ -19,6 +19,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
|
||||
('userid', models.PositiveIntegerField(verbose_name='Withings API User ID')),
|
||||
('last_update', models.DateTimeField(default=None, null=True, verbose_name='Time of last synchronization with Withings API')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -1,13 +1,13 @@
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.db import models, IntegrityError
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
import requests
|
||||
|
||||
from . import api
|
||||
|
||||
|
||||
class AccessToken(models.Model):
|
||||
account = models.OneToOneField("ApiAccount", on_delete=models.CASCADE, primary_key=True)
|
||||
@ -24,6 +24,7 @@ class RefreshToken(models.Model):
|
||||
class ApiAccount(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||
userid = models.PositiveIntegerField(verbose_name="Withings API User ID")
|
||||
last_update = models.DateTimeField(null=True, default=None, verbose_name="Time of last synchronization with Withings API")
|
||||
|
||||
def refresh_tokens(self):
|
||||
data = {
|
||||
@ -49,3 +50,41 @@ class ApiAccount(models.Model):
|
||||
self.refreshtoken.expires = now + timedelta(days=365)
|
||||
self.accesstoken.save()
|
||||
self.refreshtoken.save()
|
||||
|
||||
|
||||
def get_measurements(self, since: datetime | None = None) -> list:
|
||||
if self.accesstoken.expires < timezone.now():
|
||||
self.refresh_tokens()
|
||||
|
||||
params={
|
||||
'action': 'getmeas',
|
||||
'meastypes': '9,10,11,54,71'
|
||||
}
|
||||
if since:
|
||||
params['lastupdate'] = str(int(since.timestamp()))
|
||||
|
||||
response = requests.get(
|
||||
url="https://wbsapi.withings.net/measure",
|
||||
params=params,
|
||||
headers={
|
||||
'Authorization': f"Bearer {self.accesstoken.value}"
|
||||
}
|
||||
)
|
||||
if response is not None:
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if data['status'] != 0:
|
||||
raise RuntimeError(f"Received status {data['status']} while retrieving measurements: {data['error']}")
|
||||
|
||||
return api.parse_getmeas_response(data, self.user)
|
||||
|
||||
|
||||
def update_records(self):
|
||||
records = self.get_measurements(self.last_update)
|
||||
for record in records:
|
||||
try:
|
||||
record.save()
|
||||
except IntegrityError:
|
||||
pass
|
||||
self.last_update = timezone.now()
|
||||
self.save()
|
||||
|
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
Loading…
x
Reference in New Issue
Block a user