From 2e4f862387926f90de14a4510fdd8c82b8f4c679 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Sun, 17 Jun 2018 17:53:40 +0200 Subject: [PATCH] See #212: record user last activity date --- api/config/settings/common.py | 1 + api/funkwhale_api/users/middleware.py | 9 ++++++++ .../migrations/0008_auto_20180617_1531.py | 23 +++++++++++++++++++ api/funkwhale_api/users/models.py | 17 ++++++++++++++ api/tests/conftest.py | 8 +++++++ api/tests/users/test_middleware.py | 18 +++++++++++++++ api/tests/users/test_models.py | 17 ++++++++++++++ 7 files changed, 93 insertions(+) create mode 100644 api/funkwhale_api/users/middleware.py create mode 100644 api/funkwhale_api/users/migrations/0008_auto_20180617_1531.py create mode 100644 api/tests/users/test_middleware.py diff --git a/api/config/settings/common.py b/api/config/settings/common.py index cb5573ed5..a836dfdfd 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -146,6 +146,7 @@ MIDDLEWARE = ( "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "funkwhale_api.users.middleware.RecordActivityMiddleware", ) # MIGRATIONS CONFIGURATION diff --git a/api/funkwhale_api/users/middleware.py b/api/funkwhale_api/users/middleware.py new file mode 100644 index 000000000..d5e83f080 --- /dev/null +++ b/api/funkwhale_api/users/middleware.py @@ -0,0 +1,9 @@ +class RecordActivityMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + if hasattr(request, "user") and request.user.is_authenticated: + request.user.record_activity() + return response diff --git a/api/funkwhale_api/users/migrations/0008_auto_20180617_1531.py b/api/funkwhale_api/users/migrations/0008_auto_20180617_1531.py new file mode 100644 index 000000000..b731e3279 --- /dev/null +++ b/api/funkwhale_api/users/migrations/0008_auto_20180617_1531.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0.6 on 2018-06-17 15:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0007_auto_20180524_2009'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='last_activity', + field=models.DateTimeField(blank=True, default=None, null=True), + ), + migrations.AlterField( + model_name='user', + name='permission_library', + field=models.BooleanField(default=False, help_text='Manage library, delete files, tracks, artists, albums...', verbose_name='Manage library'), + ), + ] diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py index caf1e452b..d37656c11 100644 --- a/api/funkwhale_api/users/models.py +++ b/api/funkwhale_api/users/models.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals import binascii +import datetime import os import uuid @@ -9,6 +10,7 @@ from django.conf import settings from django.contrib.auth.models import AbstractUser from django.db import models from django.urls import reverse +from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ @@ -75,6 +77,8 @@ class User(AbstractUser): default=False, ) + last_activity = models.DateTimeField(default=None, null=True, blank=True) + def __str__(self): return self.username @@ -117,3 +121,16 @@ class User(AbstractUser): def get_activity_url(self): return settings.FUNKWHALE_URL + "/@{}".format(self.username) + + def record_activity(self): + """ + Simply update the last_activity field if current value is too old + than a threshold. This is useful to keep a track of inactive accounts. + """ + current = self.last_activity + delay = 60 * 15 # fifteen minutes + now = timezone.now() + + if current is None or current < now - datetime.timedelta(seconds=delay): + self.last_activity = now + self.save(update_fields=["last_activity"]) diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 40203ee3d..aa36e1f76 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -7,6 +7,7 @@ import pytest import requests_mock from django.contrib.auth.models import AnonymousUser from django.core.cache import cache as django_cache +from django.utils import timezone from django.test import client from dynamic_preferences.registries import global_preferences_registry from rest_framework import fields as rest_fields @@ -250,3 +251,10 @@ def to_api_date(): raise ValueError("Invalid value: {}".format(value)) return inner + + +@pytest.fixture() +def now(mocker): + now = timezone.now() + mocker.patch("django.utils.timezone.now", return_value=now) + return now diff --git a/api/tests/users/test_middleware.py b/api/tests/users/test_middleware.py new file mode 100644 index 000000000..fd13df4b3 --- /dev/null +++ b/api/tests/users/test_middleware.py @@ -0,0 +1,18 @@ +from funkwhale_api.users import middleware + + +def test_record_activity_middleware(factories, api_request, mocker): + m = middleware.RecordActivityMiddleware(lambda request: None) + user = factories["users.User"]() + record_activity = mocker.patch("funkwhale_api.users.models.User.record_activity") + request = api_request.get("/") + request.user = user + m(request) + + record_activity.assert_called_once_with() + + +def test_record_activity_middleware_no_user(api_request): + m = middleware.RecordActivityMiddleware(lambda request: None) + request = api_request.get("/") + m(request) diff --git a/api/tests/users/test_models.py b/api/tests/users/test_models.py index c73a4a1b1..74bb091e5 100644 --- a/api/tests/users/test_models.py +++ b/api/tests/users/test_models.py @@ -78,3 +78,20 @@ def test_has_permissions_and(args, perms, expected, factories): def test_has_permissions_or(args, perms, expected, factories): user = factories["users.User"](**args) assert user.has_permissions(*perms, operator="or") is expected + + +def test_record_activity(factories, now): + user = factories["users.User"]() + assert user.last_activity is None + + user.record_activity() + + assert user.last_activity == now + + +def test_record_activity_does_nothing_if_already(factories, now, mocker): + user = factories["users.User"](last_activity=now) + save = mocker.patch("funkwhale_api.users.models.User.save") + user.record_activity() + + save.assert_not_called()