from datetime import datetime, timedelta 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) value = models.CharField(max_length=256, verbose_name="Withings API Access Token") expires = models.DateTimeField(verbose_name="Time of expiration") class RefreshToken(models.Model): account = models.OneToOneField("ApiAccount", on_delete=models.CASCADE, primary_key=True) value = models.CharField(max_length=256, verbose_name="Withings API Refresh Token") expires = models.DateTimeField(verbose_name="Time of expiration") 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 = { 'action': 'requesttoken', 'client_id': settings.WITHINGS_CONFIG['CLIENT_ID'], 'client_secret': settings.WITHINGS_CONFIG['CLIENT_SECRET'], 'grant_type': 'refresh_token', 'refresh_token': self.refreshtoken.value } response = requests.post( url=settings.WITHINGS_CONFIG['ENDPOINT_URL_OAUTH2'], json=data ) if response is not None: response.raise_for_status() response_data = response.json() now = timezone.now() self.accesstoken.value = response_data['body']['access_token'] self.accesstoken.expires = now + timedelta(seconds=response_data['body']['expires_in']) self.refreshtoken.value = response_data['body']['refresh_token'] 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()