mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Merge pull request #919 from paperless-ngx/feature-settings-saved-to-db
Feature: frontend settings saved to database
This commit is contained in:
@@ -18,6 +18,7 @@ from documents.models import DocumentType
|
||||
from documents.models import SavedView
|
||||
from documents.models import SavedViewFilterRule
|
||||
from documents.models import Tag
|
||||
from documents.models import UiSettings
|
||||
from documents.settings import EXPORTER_ARCHIVE_NAME
|
||||
from documents.settings import EXPORTER_FILE_NAME
|
||||
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
||||
@@ -112,8 +113,8 @@ class Command(BaseCommand):
|
||||
map(lambda f: os.path.abspath(os.path.join(root, f)), files),
|
||||
)
|
||||
|
||||
# 2. Create manifest, containing all correspondents, types, tags and
|
||||
# documents
|
||||
# 2. Create manifest, containing all correspondents, types, tags,
|
||||
# documents and ui_settings
|
||||
with transaction.atomic():
|
||||
manifest = json.loads(
|
||||
serializers.serialize("json", Correspondent.objects.all()),
|
||||
@@ -150,6 +151,10 @@ class Command(BaseCommand):
|
||||
|
||||
manifest += json.loads(serializers.serialize("json", User.objects.all()))
|
||||
|
||||
manifest += json.loads(
|
||||
serializers.serialize("json", UiSettings.objects.all()),
|
||||
)
|
||||
|
||||
# 3. Export files from each document
|
||||
for index, document_dict in tqdm.tqdm(
|
||||
enumerate(document_manifest),
|
||||
|
39
src/documents/migrations/1019_uisettings.py
Normal file
39
src/documents/migrations/1019_uisettings.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 4.0.4 on 2022-05-07 05:10
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("documents", "1018_alter_savedviewfilterrule_value"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="UiSettings",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("settings", models.JSONField(null=True)),
|
||||
(
|
||||
"user",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="ui_settings",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
@@ -465,3 +465,17 @@ class FileInfo:
|
||||
cls._mangle_property(properties, "created")
|
||||
cls._mangle_property(properties, "title")
|
||||
return cls(**properties)
|
||||
|
||||
|
||||
# Extending User Model Using a One-To-One Link
|
||||
class UiSettings(models.Model):
|
||||
|
||||
user = models.OneToOneField(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="ui_settings",
|
||||
)
|
||||
settings = models.JSONField(null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username
|
||||
|
@@ -15,6 +15,7 @@ from .models import MatchingModel
|
||||
from .models import SavedView
|
||||
from .models import SavedViewFilterRule
|
||||
from .models import Tag
|
||||
from .models import UiSettings
|
||||
from .parsers import is_mime_type_supported
|
||||
|
||||
|
||||
@@ -505,3 +506,24 @@ class BulkDownloadSerializer(DocumentListSerializer):
|
||||
"bzip2": zipfile.ZIP_BZIP2,
|
||||
"lzma": zipfile.ZIP_LZMA,
|
||||
}[compression]
|
||||
|
||||
|
||||
class UiSettingsViewSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UiSettings
|
||||
depth = 1
|
||||
fields = [
|
||||
"id",
|
||||
"settings",
|
||||
]
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
super().update(instance, validated_data)
|
||||
return instance
|
||||
|
||||
def create(self, validated_data):
|
||||
ui_settings = UiSettings.objects.update_or_create(
|
||||
user=validated_data.get("user"),
|
||||
defaults={"settings": validated_data.get("settings", None)},
|
||||
)
|
||||
return ui_settings
|
||||
|
@@ -9,8 +9,6 @@
|
||||
<title>Paperless-ngx</title>
|
||||
<base href="{% url 'base' %}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="username" content="{{username}}">
|
||||
<meta name="full_name" content="{{full_name}}">
|
||||
<meta name="cookie_prefix" content="{{cookie_prefix}}">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
|
@@ -27,6 +27,7 @@ from documents.models import DocumentType
|
||||
from documents.models import MatchingModel
|
||||
from documents.models import SavedView
|
||||
from documents.models import Tag
|
||||
from documents.models import UiSettings
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
from paperless import version
|
||||
from rest_framework.test import APITestCase
|
||||
@@ -1398,6 +1399,41 @@ class TestDocumentApiV2(DirectoriesMixin, APITestCase):
|
||||
"#000000",
|
||||
)
|
||||
|
||||
def test_ui_settings(self):
|
||||
test_user = User.objects.create_superuser(username="test")
|
||||
self.client.force_login(user=test_user)
|
||||
|
||||
response = self.client.get("/api/ui_settings/", format="json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertDictEqual(
|
||||
response.data["settings"],
|
||||
{},
|
||||
)
|
||||
|
||||
settings = {
|
||||
"settings": {
|
||||
"dark_mode": {
|
||||
"enabled": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
"/api/ui_settings/",
|
||||
json.dumps(settings),
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get("/api/ui_settings/", format="json")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertDictEqual(
|
||||
response.data["settings"],
|
||||
settings["settings"],
|
||||
)
|
||||
|
||||
|
||||
class TestBulkEdit(DirectoriesMixin, APITestCase):
|
||||
def setUp(self):
|
||||
|
@@ -11,6 +11,7 @@ from unicodedata import normalize
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Case
|
||||
from django.db.models import Count
|
||||
from django.db.models import IntegerField
|
||||
@@ -74,6 +75,7 @@ from .serialisers import PostDocumentSerializer
|
||||
from .serialisers import SavedViewSerializer
|
||||
from .serialisers import TagSerializer
|
||||
from .serialisers import TagSerializerVersion1
|
||||
from .serialisers import UiSettingsViewSerializer
|
||||
|
||||
logger = logging.getLogger("paperless.api")
|
||||
|
||||
@@ -81,12 +83,18 @@ logger = logging.getLogger("paperless.api")
|
||||
class IndexView(TemplateView):
|
||||
template_name = "index.html"
|
||||
|
||||
def get_language(self):
|
||||
def get_frontend_language(self):
|
||||
if hasattr(
|
||||
self.request.user,
|
||||
"ui_settings",
|
||||
) and self.request.user.ui_settings.settings.get("language"):
|
||||
lang = self.request.user.ui_settings.settings.get("language")
|
||||
else:
|
||||
lang = get_language()
|
||||
# This is here for the following reason:
|
||||
# Django identifies languages in the form "en-us"
|
||||
# However, angular generates locales as "en-US".
|
||||
# this translates between these two forms.
|
||||
lang = get_language()
|
||||
if "-" in lang:
|
||||
first = lang[: lang.index("-")]
|
||||
second = lang[lang.index("-") + 1 :]
|
||||
@@ -99,16 +107,18 @@ class IndexView(TemplateView):
|
||||
context["cookie_prefix"] = settings.COOKIE_PREFIX
|
||||
context["username"] = self.request.user.username
|
||||
context["full_name"] = self.request.user.get_full_name()
|
||||
context["styles_css"] = f"frontend/{self.get_language()}/styles.css"
|
||||
context["runtime_js"] = f"frontend/{self.get_language()}/runtime.js"
|
||||
context["polyfills_js"] = f"frontend/{self.get_language()}/polyfills.js"
|
||||
context["main_js"] = f"frontend/{self.get_language()}/main.js"
|
||||
context["styles_css"] = f"frontend/{self.get_frontend_language()}/styles.css"
|
||||
context["runtime_js"] = f"frontend/{self.get_frontend_language()}/runtime.js"
|
||||
context[
|
||||
"polyfills_js"
|
||||
] = f"frontend/{self.get_frontend_language()}/polyfills.js"
|
||||
context["main_js"] = f"frontend/{self.get_frontend_language()}/main.js"
|
||||
context[
|
||||
"webmanifest"
|
||||
] = f"frontend/{self.get_language()}/manifest.webmanifest" # noqa: E501
|
||||
] = f"frontend/{self.get_frontend_language()}/manifest.webmanifest" # noqa: E501
|
||||
context[
|
||||
"apple_touch_icon"
|
||||
] = f"frontend/{self.get_language()}/apple-touch-icon.png" # noqa: E501
|
||||
] = f"frontend/{self.get_frontend_language()}/apple-touch-icon.png" # noqa: E501
|
||||
return context
|
||||
|
||||
|
||||
@@ -717,3 +727,41 @@ class RemoteVersionView(GenericAPIView):
|
||||
"feature_is_set": feature_is_set,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class UiSettingsView(GenericAPIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = UiSettingsViewSerializer
|
||||
|
||||
def get(self, request, format=None):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
user = User.objects.get(pk=request.user.id)
|
||||
displayname = user.username
|
||||
if user.first_name or user.last_name:
|
||||
displayname = " ".join([user.first_name, user.last_name])
|
||||
settings = {}
|
||||
if hasattr(user, "ui_settings"):
|
||||
settings = user.ui_settings.settings
|
||||
return Response(
|
||||
{
|
||||
"user_id": user.id,
|
||||
"username": user.username,
|
||||
"display_name": displayname,
|
||||
"settings": settings,
|
||||
},
|
||||
)
|
||||
|
||||
def post(self, request, format=None):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
},
|
||||
)
|
||||
|
@@ -20,6 +20,7 @@ from documents.views import SearchAutoCompleteView
|
||||
from documents.views import SelectionDataView
|
||||
from documents.views import StatisticsView
|
||||
from documents.views import TagViewSet
|
||||
from documents.views import UiSettingsView
|
||||
from documents.views import UnifiedSearchViewSet
|
||||
from paperless.consumers import StatusConsumer
|
||||
from paperless.views import FaviconView
|
||||
@@ -78,6 +79,11 @@ urlpatterns = [
|
||||
RemoteVersionView.as_view(),
|
||||
name="remoteversion",
|
||||
),
|
||||
re_path(
|
||||
r"^ui_settings/",
|
||||
UiSettingsView.as_view(),
|
||||
name="ui_settings",
|
||||
),
|
||||
path("token/", views.obtain_auth_token),
|
||||
]
|
||||
+ api_router.urls,
|
||||
|
Reference in New Issue
Block a user