diff --git a/src-ui/src/app/components/app-frame/app-frame.component.spec.ts b/src-ui/src/app/components/app-frame/app-frame.component.spec.ts index d548383df..9311aec96 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.spec.ts +++ b/src-ui/src/app/components/app-frame/app-frame.component.spec.ts @@ -21,7 +21,10 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { of, throwError } from 'rxjs' import { ToastService } from 'src/app/services/toast.service' -import { MessagesService } from 'src/app/services/messages.service' +import { + DjangoMessageLevel, + MessagesService, +} from 'src/app/services/messages.service' import { environment } from 'src/environments/environment' import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { ActivatedRoute, Router } from '@angular/router' @@ -401,17 +404,14 @@ describe('AppFrameComponent', () => { it('should show toasts for django messages', () => { const toastErrorSpy = jest.spyOn(toastService, 'showError') const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - jest.spyOn(messagesService, 'get').mockReturnValue( - of([ - { level: 'warning', message: 'Test warning', tags: '' }, - { level: 'error', message: 'Test error', tags: '' }, - { level: 'success', message: 'Test success', tags: '' }, - { level: 'info', message: 'Test info', tags: '' }, - { level: 'debug', message: 'Test debug', tags: '' }, - ]) - ) + jest.spyOn(messagesService, 'get').mockReturnValue([ + { level: DjangoMessageLevel.WARNING, message: 'Test warning' }, + { level: DjangoMessageLevel.ERROR, message: 'Test error' }, + { level: DjangoMessageLevel.SUCCESS, message: 'Test success' }, + { level: DjangoMessageLevel.INFO, message: 'Test info' }, + { level: DjangoMessageLevel.DEBUG, message: 'Test debug' }, + ]) component.ngOnInit() - httpTestingController.expectOne(`${environment.apiBaseUrl}messages/`) expect(toastErrorSpy).toHaveBeenCalledTimes(2) expect(toastInfoSpy).toHaveBeenCalledTimes(3) }) diff --git a/src-ui/src/app/components/app-frame/app-frame.component.ts b/src-ui/src/app/components/app-frame/app-frame.component.ts index 60008bad5..cb8d6bf4c 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.ts +++ b/src-ui/src/app/components/app-frame/app-frame.component.ts @@ -12,7 +12,10 @@ import { } from 'rxjs/operators' import { Document } from 'src/app/data/document' import { OpenDocumentsService } from 'src/app/services/open-documents.service' -import { MessagesService } from 'src/app/services/messages.service' +import { + DjangoMessageLevel, + MessagesService, +} from 'src/app/services/messages.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SearchService } from 'src/app/services/rest/search.service' import { environment } from 'src/environments/environment' @@ -95,24 +98,19 @@ export class AppFrameComponent } this.tasksService.reload() - this.messagesService - .get() - .pipe(first()) - .subscribe((msgs) => { - for (const m of msgs) { - switch (m.level) { - case 'error': - case 'warning': - this.toastService.showError(m.message) - break - case 'success': - case 'info': - case 'debug': - this.toastService.showInfo(m.message) - break - } - } - }) + this.messagesService.get().forEach((message) => { + switch (message.level) { + case DjangoMessageLevel.ERROR: + case DjangoMessageLevel.WARNING: + this.toastService.showError(message.message) + break + case DjangoMessageLevel.SUCCESS: + case DjangoMessageLevel.INFO: + case DjangoMessageLevel.DEBUG: + this.toastService.showInfo(message.message) + break + } + }) } toggleSlimSidebar(): void { diff --git a/src-ui/src/app/services/messages.service.spec.ts b/src-ui/src/app/services/messages.service.spec.ts index bf7be4464..0e9a3337b 100644 --- a/src-ui/src/app/services/messages.service.spec.ts +++ b/src-ui/src/app/services/messages.service.spec.ts @@ -1,35 +1,29 @@ import { TestBed } from '@angular/core/testing' -import { MessagesService } from './messages.service' +import { DjangoMessageLevel, MessagesService } from './messages.service' -import { - HttpClientTestingModule, - HttpTestingController, -} from '@angular/common/http/testing' import { environment } from 'src/environments/environment' +const messages = [ + { level: DjangoMessageLevel.ERROR, message: 'Error Message' }, + { level: DjangoMessageLevel.INFO, message: 'Info Message' }, +] + describe('MessagesService', () => { - let httpTestingController: HttpTestingController let service: MessagesService beforeEach(() => { + window['DJANGO_MESSAGES'] = messages TestBed.configureTestingModule({ providers: [MessagesService], - imports: [HttpClientTestingModule], }) - httpTestingController = TestBed.inject(HttpTestingController) service = TestBed.inject(MessagesService) }) - afterEach(() => { - httpTestingController.verify() - }) + it('calls retrieves global django messages if present', () => { + expect(service.get()).toEqual(messages) - it('calls messages endpoint', () => { - service.get().subscribe() - const req = httpTestingController.expectOne( - `${environment.apiBaseUrl}messages/` - ) - expect(req.request.method).toEqual('GET') + window['DJANGO_MESSAGES'] = undefined + expect(service.get()).toEqual([]) }) }) diff --git a/src-ui/src/app/services/messages.service.ts b/src-ui/src/app/services/messages.service.ts index 8177f52c7..89548b96c 100644 --- a/src-ui/src/app/services/messages.service.ts +++ b/src-ui/src/app/services/messages.service.ts @@ -1,25 +1,27 @@ -import { HttpClient } from '@angular/common/http' import { Injectable } from '@angular/core' -import { Observable } from 'rxjs' -import { environment } from 'src/environments/environment' + +// see https://docs.djangoproject.com/en/5.0/ref/contrib/messages/#message-tags +export enum DjangoMessageLevel { + DEBUG = 'debug', + INFO = 'info', + SUCCESS = 'success', + WARNING = 'warning', + ERROR = 'error', +} export interface DjangoMessage { - level: string + level: DjangoMessageLevel message: string - tags: string } @Injectable({ providedIn: 'root', }) export class MessagesService { - private endpoint = 'messages' + constructor() {} - constructor(private http: HttpClient) {} - - get(): Observable { - return this.http.get( - `${environment.apiBaseUrl}${this.endpoint}/` - ) + get(): DjangoMessage[] { + // These are embedded in the HTML as raw JS, kept as a service for convenience + return window['DJANGO_MESSAGES'] ?? [] } } diff --git a/src/documents/templates/account/login.html b/src/documents/templates/account/login.html index 65bd3a27f..777f65409 100644 --- a/src/documents/templates/account/login.html +++ b/src/documents/templates/account/login.html @@ -39,6 +39,11 @@ + {% for message in messages %} + + {% endfor %}

{% translate "Please sign in." %}

{% if form.errors %} + diff --git a/src/documents/templates/socialaccount/login.html b/src/documents/templates/socialaccount/login.html index dc852d047..f135a77fe 100644 --- a/src/documents/templates/socialaccount/login.html +++ b/src/documents/templates/socialaccount/login.html @@ -12,7 +12,7 @@ - {% translate "Paperless-ngx sign in" %} + {% translate "Paperless-ngx social account sign in" %} diff --git a/src/documents/templates/socialaccount/signup.html b/src/documents/templates/socialaccount/signup.html index e60f010fb..ef208d8ad 100644 --- a/src/documents/templates/socialaccount/signup.html +++ b/src/documents/templates/socialaccount/signup.html @@ -11,7 +11,7 @@ - {% translate "Paperless-ngx sign up" %} + {% translate "Paperless-ngx social account sign up" %} diff --git a/src/documents/tests/test_api_messages.py b/src/documents/tests/test_api_messages.py deleted file mode 100644 index 82d2390fb..000000000 --- a/src/documents/tests/test_api_messages.py +++ /dev/null @@ -1,55 +0,0 @@ -from django.contrib import messages -from django.contrib.auth.models import User -from django.contrib.messages.storage.fallback import FallbackStorage -from django.test import RequestFactory -from rest_framework import status -from rest_framework.test import APITestCase - -from documents.tests.utils import DirectoriesMixin -from paperless.views import MessagesView - - -class TestApiMessages(DirectoriesMixin, APITestCase): - ENDPOINT = "/api/messages/" - - def setUp(self): - super().setUp() - - self.user = User.objects.create_superuser( - username="temp_admin", - first_name="firstname", - last_name="surname", - ) - self.client.force_authenticate(user=self.user) - - def test_get_django_messages(self): - """ - GIVEN: - - Configured user - - Pending django message - WHEN: - - API call is made to get the django messages - THEN: - - Pending message is returned - - No more messages are pending - """ - - factory = RequestFactory() - - request = factory.get(self.ENDPOINT) - request.user = self.user - - # Fake middleware support for RequestFactory - # See https://stackoverflow.com/a/66473588/1022690 - setattr(request, "session", "session") - setattr(request, "_messages", FallbackStorage(request)) - - msg = "Test message" - messages.error(request, msg) - - response = MessagesView.as_view()(request) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]["level"], "error") - self.assertEqual(response.data[0]["message"], msg) diff --git a/src/paperless/urls.py b/src/paperless/urls.py index 4d269fcaf..74f6fc108 100644 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -45,7 +45,6 @@ from paperless.views import DisconnectSocialAccountView from paperless.views import FaviconView from paperless.views import GenerateAuthTokenView from paperless.views import GroupViewSet -from paperless.views import MessagesView from paperless.views import ProfileView from paperless.views import SocialAccountProvidersView from paperless.views import UserViewSet @@ -148,7 +147,6 @@ urlpatterns = [ ProfileView.as_view(), name="profile_view", ), - path("messages/", MessagesView.as_view()), *api_router.urls, ], ), diff --git a/src/paperless/views.py b/src/paperless/views.py index 97018b36e..cf23f6181 100644 --- a/src/paperless/views.py +++ b/src/paperless/views.py @@ -3,7 +3,6 @@ from collections import OrderedDict from allauth.socialaccount.adapter import get_adapter from allauth.socialaccount.models import SocialAccount -from django.contrib import messages from django.contrib.auth.models import Group from django.contrib.auth.models import User from django.db.models.functions import Lower @@ -227,18 +226,3 @@ class SocialAccountProvidersView(APIView): ] return Response(sorted(resp, key=lambda p: p["name"])) - - -class MessagesView(APIView): - """ - Expose django messages. This clears the messages in django - """ - - permission_classes = [IsAuthenticated] - - def get(self, request, *args, **kwargs): - data = [ - {"level": m.level_tag, "message": m.message, "tags": m.extra_tags} - for m in messages.get_messages(request) - ] - return Response(data)