Add API for django messages
This commit is contained in:
parent
2e597a7176
commit
36db77cf89
@ -21,6 +21,7 @@ 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 { 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'
|
||||||
@ -83,6 +84,7 @@ describe('AppFrameComponent', () => {
|
|||||||
let permissionsService: PermissionsService
|
let permissionsService: PermissionsService
|
||||||
let remoteVersionService: RemoteVersionService
|
let remoteVersionService: RemoteVersionService
|
||||||
let toastService: ToastService
|
let toastService: ToastService
|
||||||
|
let messagesService: MessagesService
|
||||||
let openDocumentsService: OpenDocumentsService
|
let openDocumentsService: OpenDocumentsService
|
||||||
let searchService: SearchService
|
let searchService: SearchService
|
||||||
let documentListViewService: DocumentListViewService
|
let documentListViewService: DocumentListViewService
|
||||||
@ -123,6 +125,7 @@ describe('AppFrameComponent', () => {
|
|||||||
RemoteVersionService,
|
RemoteVersionService,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
ToastService,
|
ToastService,
|
||||||
|
MessagesService,
|
||||||
OpenDocumentsService,
|
OpenDocumentsService,
|
||||||
SearchService,
|
SearchService,
|
||||||
NgbModal,
|
NgbModal,
|
||||||
@ -151,6 +154,7 @@ describe('AppFrameComponent', () => {
|
|||||||
permissionsService = TestBed.inject(PermissionsService)
|
permissionsService = TestBed.inject(PermissionsService)
|
||||||
remoteVersionService = TestBed.inject(RemoteVersionService)
|
remoteVersionService = TestBed.inject(RemoteVersionService)
|
||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
|
messagesService = TestBed.inject(MessagesService)
|
||||||
openDocumentsService = TestBed.inject(OpenDocumentsService)
|
openDocumentsService = TestBed.inject(OpenDocumentsService)
|
||||||
searchService = TestBed.inject(SearchService)
|
searchService = TestBed.inject(SearchService)
|
||||||
documentListViewService = TestBed.inject(DocumentListViewService)
|
documentListViewService = TestBed.inject(DocumentListViewService)
|
||||||
@ -393,4 +397,19 @@ describe('AppFrameComponent', () => {
|
|||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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: 'error', message: 'Test error', tags: '' },
|
||||||
|
{ level: 'info', message: 'Test info', tags: '' },
|
||||||
|
])
|
||||||
|
)
|
||||||
|
component.ngOnInit()
|
||||||
|
httpTestingController.expectOne(`${environment.apiBaseUrl}messages/`)
|
||||||
|
expect(toastErrorSpy).toHaveBeenCalled()
|
||||||
|
expect(toastInfoSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -12,6 +12,7 @@ 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 { 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'
|
||||||
@ -73,7 +74,8 @@ export class AppFrameComponent
|
|||||||
public tasksService: TasksService,
|
public tasksService: TasksService,
|
||||||
private readonly toastService: ToastService,
|
private readonly toastService: ToastService,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
permissionsService: PermissionsService
|
permissionsService: PermissionsService,
|
||||||
|
private messagesService: MessagesService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@ -92,6 +94,25 @@ export class AppFrameComponent
|
|||||||
this.checkForUpdates()
|
this.checkForUpdates()
|
||||||
}
|
}
|
||||||
this.tasksService.reload()
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSlimSidebar(): void {
|
toggleSlimSidebar(): void {
|
||||||
|
35
src-ui/src/app/services/messages.service.spec.ts
Normal file
35
src-ui/src/app/services/messages.service.spec.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing'
|
||||||
|
|
||||||
|
import { MessagesService } from './messages.service'
|
||||||
|
|
||||||
|
import {
|
||||||
|
HttpClientTestingModule,
|
||||||
|
HttpTestingController,
|
||||||
|
} from '@angular/common/http/testing'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
|
|
||||||
|
describe('MessagesService', () => {
|
||||||
|
let httpTestingController: HttpTestingController
|
||||||
|
let service: MessagesService
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [MessagesService],
|
||||||
|
imports: [HttpClientTestingModule],
|
||||||
|
})
|
||||||
|
httpTestingController = TestBed.inject(HttpTestingController)
|
||||||
|
service = TestBed.inject(MessagesService)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
httpTestingController.verify()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls get profile endpoint', () => {
|
||||||
|
service.get().subscribe()
|
||||||
|
const req = httpTestingController.expectOne(
|
||||||
|
`${environment.apiBaseUrl}messages/`
|
||||||
|
)
|
||||||
|
expect(req.request.method).toEqual('GET')
|
||||||
|
})
|
||||||
|
})
|
25
src-ui/src/app/services/messages.service.ts
Normal file
25
src-ui/src/app/services/messages.service.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { Observable } from 'rxjs'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
|
|
||||||
|
export interface DjangoMessage {
|
||||||
|
level: string
|
||||||
|
message: string
|
||||||
|
tags: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class MessagesService {
|
||||||
|
private endpoint = 'messages'
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
get(): Observable<DjangoMessage[]> {
|
||||||
|
return this.http.get<DjangoMessage[]>(
|
||||||
|
`${environment.apiBaseUrl}${this.endpoint}/`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
55
src/documents/tests/test_api_messages.py
Normal file
55
src/documents/tests/test_api_messages.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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,6 +45,7 @@ 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
|
||||||
@ -147,6 +148,7 @@ urlpatterns = [
|
|||||||
ProfileView.as_view(),
|
ProfileView.as_view(),
|
||||||
name="profile_view",
|
name="profile_view",
|
||||||
),
|
),
|
||||||
|
path("messages/", MessagesView.as_view()),
|
||||||
*api_router.urls,
|
*api_router.urls,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -2,6 +2,7 @@ import os
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from allauth.socialaccount.adapter import get_adapter
|
from allauth.socialaccount.adapter import get_adapter
|
||||||
|
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
|
||||||
@ -222,3 +223,18 @@ 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