merge experimental #1

Merged
jlobbes merged 25 commits from experimental into master 2023-08-17 15:16:51 +01:00
20 changed files with 345 additions and 61 deletions
Showing only changes of commit 1816fc6e2e - Show all commits

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -108,7 +108,8 @@ def register_continue(request):
instance.save() instance.save()
request.session.flush() 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 # TODO redirect user to some other page and ask them to log in
return redirect('dashboard') return redirect('dashboard')

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -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.conf import settings
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -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.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -20,7 +20,7 @@ class Migration(migrations.Migration):
name='BloodPressureRecord', name='BloodPressureRecord',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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_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)')), ('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)), ('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', name='BodyTempRecord',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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_celsius', models.PositiveIntegerField(validators=[medwings.validators.BodyTempRecordValidator.value_celsius], verbose_name='Body Temperature (°C)')), ('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)), ('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', name='HeartRateRecord',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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)')), ('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)), ('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', name='Spo2LevelRecord',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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 (%)')), ('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)), ('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', name='RespirationScoreRecord',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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')), ('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)), ('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', name='MewsRecord',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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')), ('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')), ('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')), ('body_temp_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='medwings.bodytemprecord')),

View File

@ -19,23 +19,29 @@ class Profile(models.Model):
class BloodPressureRecord(models.Model): class BloodPressureRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) 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_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)") value_diastolic_mmhg = models.PositiveIntegerField(validators=[validators.BloodPressureRecordValidator.value_diastolic_mmhg], verbose_name="Diastolic Blood Pressure (mmhg)")
class BodyTempRecord(models.Model): class BodyTempRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
recorded = models.DateTimeField(validators=[validators.BodyTempRecordValidator.recorded], verbose_name="Time at which measurement was taken") recorded = models.DateTimeField(validators=[validators.BodyTempRecordValidator.recorded], unique=True, verbose_name="Time at which measurement was taken")
value_celsius = models.PositiveIntegerField(validators=[validators.BodyTempRecordValidator.value_celsius], verbose_name="Body Temperature (\u00B0C)") 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): class HeartRateRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) 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)") 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): class RespirationScoreRecord(models.Model):
SEVERITY_NONE = 0 SEVERITY_NONE = 0
SEVERITY_LOW = 1 SEVERITY_LOW = 1
@ -47,19 +53,13 @@ class RespirationScoreRecord(models.Model):
] ]
user = models.ForeignKey(User, on_delete=models.CASCADE) 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") 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): class MewsRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) 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") 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) blood_pressure_record = models.ForeignKey(BloodPressureRecord, on_delete=models.CASCADE)

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -5,6 +5,7 @@ Django==4.2.3
idna==3.4 idna==3.4
psycopg==3.1.9 psycopg==3.1.9
psycopg-binary==3.1.9 psycopg-binary==3.1.9
pytz==2023.3
requests==2.31.0 requests==2.31.0
sqlparse==0.4.4 sqlparse==0.4.4
typing_extensions==4.7.1 typing_extensions==4.7.1

210
app/withings/README.md Normal file
View 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
}
]
}
}
```

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,11 +1,15 @@
from datetime import timedelta from datetime import datetime, timedelta
from random import randint from random import randint
import requests import requests
import pytz
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.models import User
from urllib.parse import urlencode from urllib.parse import urlencode
from medwings import models as mm
def fetch_initial_tokens(authorization_code, redirect_uri): def fetch_initial_tokens(authorization_code, redirect_uri):
data = { data = {
'action': 'requesttoken', 'action': 'requesttoken',
@ -49,3 +53,64 @@ def save_tokens_to_session(request, response_data):
now = timezone.now() now = timezone.now()
request.session['withings_access_token_expiry'] = (now + timedelta(seconds=response_data['body']['expires_in'])).isoformat() 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() 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

View File

@ -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.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -19,6 +19,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), ('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')), ('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( migrations.CreateModel(

View File

@ -1,13 +1,13 @@
from datetime import timedelta from datetime import datetime, timedelta
import logging
from django.db import models from django.db import models, IntegrityError
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
import requests import requests
from . import api
class AccessToken(models.Model): class AccessToken(models.Model):
account = models.OneToOneField("ApiAccount", on_delete=models.CASCADE, primary_key=True) account = models.OneToOneField("ApiAccount", on_delete=models.CASCADE, primary_key=True)
@ -24,6 +24,7 @@ class RefreshToken(models.Model):
class ApiAccount(models.Model): class ApiAccount(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
userid = models.PositiveIntegerField(verbose_name="Withings API User ID") 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): def refresh_tokens(self):
data = { data = {
@ -49,3 +50,41 @@ class ApiAccount(models.Model):
self.refreshtoken.expires = now + timedelta(days=365) self.refreshtoken.expires = now + timedelta(days=365)
self.accesstoken.save() self.accesstoken.save()
self.refreshtoken.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()

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.