Commit 84d49754 authored by Agate's avatar Agate 💬

Fix #996: Persist theme and language settings accross sessions

parent f6a81a9e
# Generated by Django 3.0.8 on 2020-07-05 08:29
import django.contrib.postgres.fields.jsonb
from django.db import migrations
import funkwhale_api.users.models
class Migration(migrations.Migration):
dependencies = [
('users', '0017_actor_avatar'),
]
operations = [
migrations.AlterModelManagers(
name='user',
managers=[
('objects', funkwhale_api.users.models.UserManager()),
],
),
migrations.AddField(
model_name='user',
name='settings',
field=django.contrib.postgres.fields.jsonb.JSONField(default=None, null=True, blank=True, max_length=50000),
),
]
......@@ -10,7 +10,8 @@ import uuid
from django.conf import settings
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
from django.db import models
from django.contrib.postgres.fields import JSONField
from django.db import models, transaction
from django.dispatch import receiver
from django.urls import reverse
from django.utils import timezone
......@@ -191,6 +192,7 @@ class User(AbstractUser):
null=True,
blank=True,
)
settings = JSONField(default=None, null=True, blank=True, max_length=50000)
objects = UserManager()
......@@ -213,6 +215,16 @@ class User(AbstractUser):
def all_permissions(self):
return self.get_permissions()
@transaction.atomic
def set_settings(self, **settings):
u = self.__class__.objects.select_for_update().get(pk=self.pk)
if not u.settings:
u.settings = {}
for key, value in settings.items():
u.settings[key] = value
u.save(update_fields=["settings"])
self.settings = u.settings
def has_permissions(self, *perms, **kwargs):
operator = kwargs.pop("operator", "and")
if operator not in ["and", "or"]:
......
......@@ -232,6 +232,7 @@ class MeSerializer(UserReadSerializer):
"funkwhale_support_message_display_date",
"summary",
"tokens",
"settings",
]
def get_quota_status(self, o):
......
......@@ -80,6 +80,13 @@ class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
serializer = serializers.MeSerializer(request.user)
return Response(serializer.data)
@action(methods=["post"], detail=False, url_name="settings", url_path="settings")
def set_settings(self, request, *args, **kwargs):
"""Return information about the current user or delete it"""
new_settings = request.data
request.user.set_settings(**new_settings)
return Response(request.user.settings)
@action(
methods=["get", "post", "delete"],
required_scope="security",
......
......@@ -259,3 +259,16 @@ def test_get_by_natural_key_annotates_primary_email_verified_false(factories):
user = models.User.objects.get_by_natural_key(user.username)
assert user.has_verified_primary_email is False
def test_set_settings(factories):
user = factories["users.User"]()
assert user.settings is None
user.set_settings(foo="bar", hello="world")
user.refresh_from_db()
assert user.settings == {
"foo": "bar",
"hello": "world",
}
......@@ -57,3 +57,10 @@ def test_me_serializer_includes_tokens(factories, mocker):
generate_scoped_token.assert_called_once_with(
user_id=user.pk, user_secret=user.secret_key, scopes=["read:libraries"]
)
def test_me_serializer_includes_settings(factories):
user = factories["users.User"](settings={"foo": "bar"})
serializer = serializers.MeSerializer(user)
assert serializer.data["settings"] == {"foo": "bar"}
......@@ -556,3 +556,15 @@ def test_logout(api_client, factories, mocker):
response = api_client.post(url)
assert response.status_code == 200
assert auth_logout.call_count == 1
def test_update_settings(logged_in_api_client, factories):
logged_in_api_client.user.set_settings(foo="bar")
url = reverse("api:v1:users:users-settings")
payload = {"theme": "dark"}
response = logged_in_api_client.post(url, payload, format="json")
assert response.status_code == 200
logged_in_api_client.user.refresh_from_db()
assert logged_in_api_client.user.settings == {"foo": "bar", "theme": "dark"}
Persist theme and language settings accross sessions (#996)
\ No newline at end of file
......@@ -23,7 +23,7 @@
<div class="ui form">
<div class="ui field">
<label><translate translate-context="Footer/Settings/Dropdown.Label/Short, Verb">Change language</translate></label>
<select class="ui dropdown" :value="$language.current" @change="$store.commit('ui/currentLanguage', $event.target.value)">
<select class="ui dropdown" :value="$language.current" @change="$store.dispatch('ui/currentLanguage', $event.target.value)">
<option v-for="(language, key) in $language.available" :key="key" :value="key">{{ language }}</option>
</select>
</div>
......@@ -39,7 +39,7 @@
<div class="ui form">
<div class="ui field">
<label><translate translate-context="Footer/Settings/Dropdown.Label/Short, Verb">Change theme</translate></label>
<select class="ui dropdown" :value="$store.state.ui.theme" @change="$store.commit('ui/theme', $event.target.value)">
<select class="ui dropdown" :value="$store.state.ui.theme" @change="$store.dispatch('ui/theme', $event.target.value)">
<option v-for="theme in themes" :key="theme.key" :value="theme.key">{{ theme.name }}</option>
</select>
</div>
......
......@@ -184,9 +184,11 @@ export default {
return new Promise((resolve, reject) => {
axios.get('users/users/me/').then((response) => {
logger.default.info('Successfully fetched user profile')
dispatch('ui/initSettings', response.data.settings, { root: true })
dispatch('updateProfile', response.data).then(() => {
resolve(response.data)
})
dispatch('ui/fetchUnreadNotifications', null, { root: true })
if (response.data.permissions.library) {
dispatch('ui/fetchPendingReviewEdits', null, { root: true })
......
......@@ -278,6 +278,30 @@ export default {
commit('notifications', {type: 'pendingReviewRequests', count: response.data.count})
})
},
async currentLanguage ({state, commit, rootState}, value) {
commit("currentLanguage", value)
if (rootState.auth.authenticated) {
await axios.post("users/settings", {"language": value})
}
},
async theme ({state, commit, rootState}, value) {
commit("theme", value)
if (rootState.auth.authenticated) {
await axios.post("users/settings", {"theme": value})
}
},
async initSettings ({commit}, settings) {
settings = settings || {}
if (settings.language) {
commit("currentLanguage", settings.language)
}
if (settings.theme) {
commit("theme", settings.theme)
}
},
websocketEvent ({state}, event) {
let handlers = state.websocketEventsHandlers[event.type]
console.log('Dispatching websocket event', event, handlers)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment