Pass Django messages to frontend without API endpoint
This commit is contained in:
parent
24ec256580
commit
93750663aa
@ -21,7 +21,10 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
|
|||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { of, throwError } from 'rxjs'
|
import { of, throwError } from 'rxjs'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
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 { environment } from 'src/environments/environment'
|
||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
@ -401,17 +404,14 @@ describe('AppFrameComponent', () => {
|
|||||||
it('should show toasts for django messages', () => {
|
it('should show toasts for django messages', () => {
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
jest.spyOn(messagesService, 'get').mockReturnValue(
|
jest.spyOn(messagesService, 'get').mockReturnValue([
|
||||||
of([
|
{ level: DjangoMessageLevel.WARNING, message: 'Test warning' },
|
||||||
{ level: 'warning', message: 'Test warning', tags: '' },
|
{ level: DjangoMessageLevel.ERROR, message: 'Test error' },
|
||||||
{ level: 'error', message: 'Test error', tags: '' },
|
{ level: DjangoMessageLevel.SUCCESS, message: 'Test success' },
|
||||||
{ level: 'success', message: 'Test success', tags: '' },
|
{ level: DjangoMessageLevel.INFO, message: 'Test info' },
|
||||||
{ level: 'info', message: 'Test info', tags: '' },
|
{ level: DjangoMessageLevel.DEBUG, message: 'Test debug' },
|
||||||
{ level: 'debug', message: 'Test debug', tags: '' },
|
])
|
||||||
])
|
|
||||||
)
|
|
||||||
component.ngOnInit()
|
component.ngOnInit()
|
||||||
httpTestingController.expectOne(`${environment.apiBaseUrl}messages/`)
|
|
||||||
expect(toastErrorSpy).toHaveBeenCalledTimes(2)
|
expect(toastErrorSpy).toHaveBeenCalledTimes(2)
|
||||||
expect(toastInfoSpy).toHaveBeenCalledTimes(3)
|
expect(toastInfoSpy).toHaveBeenCalledTimes(3)
|
||||||
})
|
})
|
||||||
|
@ -12,7 +12,10 @@ import {
|
|||||||
} from 'rxjs/operators'
|
} from 'rxjs/operators'
|
||||||
import { Document } from 'src/app/data/document'
|
import { Document } from 'src/app/data/document'
|
||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
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 { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { SearchService } from 'src/app/services/rest/search.service'
|
import { SearchService } from 'src/app/services/rest/search.service'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
@ -95,24 +98,19 @@ export class AppFrameComponent
|
|||||||
}
|
}
|
||||||
this.tasksService.reload()
|
this.tasksService.reload()
|
||||||
|
|
||||||
this.messagesService
|
this.messagesService.get().forEach((message) => {
|
||||||
.get()
|
switch (message.level) {
|
||||||
.pipe(first())
|
case DjangoMessageLevel.ERROR:
|
||||||
.subscribe((msgs) => {
|
case DjangoMessageLevel.WARNING:
|
||||||
for (const m of msgs) {
|
this.toastService.showError(message.message)
|
||||||
switch (m.level) {
|
break
|
||||||
case 'error':
|
case DjangoMessageLevel.SUCCESS:
|
||||||
case 'warning':
|
case DjangoMessageLevel.INFO:
|
||||||
this.toastService.showError(m.message)
|
case DjangoMessageLevel.DEBUG:
|
||||||
break
|
this.toastService.showInfo(message.message)
|
||||||
case 'success':
|
break
|
||||||
case 'info':
|
}
|
||||||
case 'debug':
|
})
|
||||||
this.toastService.showInfo(m.message)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSlimSidebar(): void {
|
toggleSlimSidebar(): void {
|
||||||
|
@ -1,35 +1,29 @@
|
|||||||
import { TestBed } from '@angular/core/testing'
|
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'
|
import { environment } from 'src/environments/environment'
|
||||||
|
|
||||||
|
const messages = [
|
||||||
|
{ level: DjangoMessageLevel.ERROR, message: 'Error Message' },
|
||||||
|
{ level: DjangoMessageLevel.INFO, message: 'Info Message' },
|
||||||
|
]
|
||||||
|
|
||||||
describe('MessagesService', () => {
|
describe('MessagesService', () => {
|
||||||
let httpTestingController: HttpTestingController
|
|
||||||
let service: MessagesService
|
let service: MessagesService
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
window['DJANGO_MESSAGES'] = messages
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [MessagesService],
|
providers: [MessagesService],
|
||||||
imports: [HttpClientTestingModule],
|
|
||||||
})
|
})
|
||||||
httpTestingController = TestBed.inject(HttpTestingController)
|
|
||||||
service = TestBed.inject(MessagesService)
|
service = TestBed.inject(MessagesService)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
it('calls retrieves global django messages if present', () => {
|
||||||
httpTestingController.verify()
|
expect(service.get()).toEqual(messages)
|
||||||
})
|
|
||||||
|
|
||||||
it('calls messages endpoint', () => {
|
window['DJANGO_MESSAGES'] = undefined
|
||||||
service.get().subscribe()
|
expect(service.get()).toEqual([])
|
||||||
const req = httpTestingController.expectOne(
|
|
||||||
`${environment.apiBaseUrl}messages/`
|
|
||||||
)
|
|
||||||
expect(req.request.method).toEqual('GET')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
import { HttpClient } from '@angular/common/http'
|
|
||||||
import { Injectable } from '@angular/core'
|
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 {
|
export interface DjangoMessage {
|
||||||
level: string
|
level: DjangoMessageLevel
|
||||||
message: string
|
message: string
|
||||||
tags: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class MessagesService {
|
export class MessagesService {
|
||||||
private endpoint = 'messages'
|
constructor() {}
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
get(): DjangoMessage[] {
|
||||||
|
// These are embedded in the HTML as raw JS, kept as a service for convenience
|
||||||
get(): Observable<DjangoMessage[]> {
|
return window['DJANGO_MESSAGES'] ?? []
|
||||||
return this.http.get<DjangoMessage[]>(
|
|
||||||
`${environment.apiBaseUrl}${this.endpoint}/`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)"/>
|
<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>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.level_tag }}" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
<p>{% translate "Please sign in." %}</p>
|
<p>{% translate "Please sign in." %}</p>
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
|
@ -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>
|
<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>
|
||||||
</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>
|
</pngx-root>
|
||||||
<script src="{% static runtime_js %}" defer></script>
|
<script src="{% static runtime_js %}" defer></script>
|
||||||
<script src="{% static polyfills_js %}" defer></script>
|
<script src="{% static polyfills_js %}" defer></script>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<meta name="author" content="Paperless-ngx project and contributors">
|
<meta name="author" content="Paperless-ngx project and contributors">
|
||||||
<meta name="robots" content="noindex,nofollow">
|
<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 'bootstrap.min.css' %}" rel="stylesheet">
|
||||||
<link href="{% static 'signin.css' %}" rel="stylesheet">
|
<link href="{% static 'signin.css' %}" rel="stylesheet">
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<meta name="author" content="Paperless-ngx project and contributors">
|
<meta name="author" content="Paperless-ngx project and contributors">
|
||||||
<meta name="robots" content="noindex,nofollow">
|
<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 'bootstrap.min.css' %}" rel="stylesheet">
|
||||||
<link href="{% static 'signin.css' %}" rel="stylesheet">
|
<link href="{% static 'signin.css' %}" rel="stylesheet">
|
||||||
|
@ -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)
|
|
@ -45,7 +45,6 @@ from paperless.views import DisconnectSocialAccountView
|
|||||||
from paperless.views import FaviconView
|
from paperless.views import FaviconView
|
||||||
from paperless.views import GenerateAuthTokenView
|
from paperless.views import GenerateAuthTokenView
|
||||||
from paperless.views import GroupViewSet
|
from paperless.views import GroupViewSet
|
||||||
from paperless.views import MessagesView
|
|
||||||
from paperless.views import ProfileView
|
from paperless.views import ProfileView
|
||||||
from paperless.views import SocialAccountProvidersView
|
from paperless.views import SocialAccountProvidersView
|
||||||
from paperless.views import UserViewSet
|
from paperless.views import UserViewSet
|
||||||
@ -148,7 +147,6 @@ urlpatterns = [
|
|||||||
ProfileView.as_view(),
|
ProfileView.as_view(),
|
||||||
name="profile_view",
|
name="profile_view",
|
||||||
),
|
),
|
||||||
path("messages/", MessagesView.as_view()),
|
|
||||||
*api_router.urls,
|
*api_router.urls,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,6 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
from allauth.socialaccount.adapter import get_adapter
|
from allauth.socialaccount.adapter import get_adapter
|
||||||
from allauth.socialaccount.models import SocialAccount
|
from allauth.socialaccount.models import SocialAccount
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
@ -227,18 +226,3 @@ class SocialAccountProvidersView(APIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
return Response(sorted(resp, key=lambda p: p["name"]))
|
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)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user