Pass Django messages to frontend without API endpoint

This commit is contained in:
shamoon 2024-01-05 10:48:12 -08:00 committed by Moritz Pflanzer
parent 24ec256580
commit 93750663aa
11 changed files with 67 additions and 134 deletions

View File

@ -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)
})

View File

@ -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 {

View File

@ -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([])
})
})

View File

@ -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<DjangoMessage[]> {
return this.http.get<DjangoMessage[]>(
`${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'] ?? []
}
}

View File

@ -39,6 +39,11 @@
<path d="M757.6,293.7c-20-10.8-42.6-16.2-67.8-16.2H600c-8.5,39.2-21.1,76.4-37.6,111.3c-9.9,20.8-21.1,40.6-33.6,59.4v207.2h88.9 V521.5h72c25.2,0,47.8-5.4,67.8-16.2s35.7-25.6,47.1-44.2c11.4-18.7,17.1-39.1,17.1-61.3c0.1-22.7-5.6-43.3-17-61.9 C793.3,319.2,777.6,304.5,757.6,293.7z M716.6,434.3c-9.3,8.9-21.6,13.3-36.7,13.3l-62.2,0.4v-92.5l62.2-0.4 c15.1,0,27.3,4.4,36.7,13.3c9.4,8.9,14,19.9,14,32.9C730.6,414.5,726,425.4,716.6,434.3z" transform="translate(0)"/>
</g>
</svg>
{% for message in messages %}
<div class="alert alert-{{ message.level_tag }}" role="alert">
{{ message }}
</div>
{% endfor %}
<p>{% translate "Please sign in." %}</p>
{% if form.errors %}
<div class="alert alert-danger" role="alert">

View File

@ -80,6 +80,13 @@
<p class="warning m-auto mt-3 small fade hide">{% translate "Still here?! Hmm, something might be wrong." %} <a href="https://docs.paperless-ngx.com">{% translate "Here's a link to the docs." %}</a></p>
</div>
</div>
<script type="text/javascript">{# Pass Django messages to Angular frontend #}
window.DJANGO_MESSAGES = [
{% for message in messages %}
{ level: "{{ message.level_tag | escapejs }}", message: "{{ message | escapejs }}" },
{% endfor %}
]
</script>
</pngx-root>
<script src="{% static runtime_js %}" defer></script>
<script src="{% static polyfills_js %}" defer></script>

View File

@ -12,7 +12,7 @@
<meta name="author" content="Paperless-ngx project and contributors">
<meta name="robots" content="noindex,nofollow">
<title>{% translate "Paperless-ngx sign in" %}</title>
<title>{% translate "Paperless-ngx social account sign in" %}</title>
<link href="{% static 'bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'signin.css' %}" rel="stylesheet">

View File

@ -11,7 +11,7 @@
<meta name="author" content="Paperless-ngx project and contributors">
<meta name="robots" content="noindex,nofollow">
<title>{% translate "Paperless-ngx sign up" %}</title>
<title>{% translate "Paperless-ngx social account sign up" %}</title>
<link href="{% static 'bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'signin.css' %}" rel="stylesheet">

View File

@ -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)

View File

@ -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,
],
),

View File

@ -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)