From f2dd9659ff82d123a70b1a3d884c2674a5f2c0b5 Mon Sep 17 00:00:00 2001 From: Hironsan Date: Wed, 2 Feb 2022 10:18:54 +0900 Subject: [PATCH 1/3] Enable to paginate project list api --- backend/api/tests/api/test_project.py | 6 +++--- backend/api/views/project.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/api/tests/api/test_project.py b/backend/api/tests/api/test_project.py index cf8efc3a..960d68af 100644 --- a/backend/api/tests/api/test_project.py +++ b/backend/api/tests/api/test_project.py @@ -15,13 +15,13 @@ class TestProjectList(CRUDMixin): def test_return_projects_to_member(self): for member in self.project.members: response = self.assert_fetch(member, status.HTTP_200_OK) - project = response.data[0] - self.assertEqual(len(response.data), 1) + project = response.data['results'][0] + self.assertEqual(response.data['count'], 1) self.assertEqual(project['id'], self.project.item.id) def test_does_not_return_project_to_non_member(self): response = self.assert_fetch(self.non_member, status.HTTP_200_OK) - self.assertEqual(len(response.data), 0) + self.assertEqual(response.data['count'], 0) class TestProjectCreate(CRUDMixin): diff --git a/backend/api/views/project.py b/backend/api/views/project.py index 57ac61c0..7f62b431 100644 --- a/backend/api/views/project.py +++ b/backend/api/views/project.py @@ -11,7 +11,6 @@ from ..serializers import ProjectPolymorphicSerializer class ProjectList(generics.ListCreateAPIView): serializer_class = ProjectPolymorphicSerializer - pagination_class = None def get_permissions(self): if self.request.method == 'GET': From 11d5c4acdaeed6467fd0c21d2e3d628f72e613d2 Mon Sep 17 00:00:00 2001 From: Hironsan Date: Wed, 2 Feb 2022 11:14:47 +0900 Subject: [PATCH 2/3] Enable to search projects --- backend/api/views/project.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/api/views/project.py b/backend/api/views/project.py index 7f62b431..6f06bca9 100644 --- a/backend/api/views/project.py +++ b/backend/api/views/project.py @@ -1,5 +1,6 @@ from django.conf import settings -from rest_framework import generics, status +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import filters, generics, status from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.response import Response @@ -11,6 +12,8 @@ from ..serializers import ProjectPolymorphicSerializer class ProjectList(generics.ListCreateAPIView): serializer_class = ProjectPolymorphicSerializer + filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) + search_fields = ('name', 'description') def get_permissions(self): if self.request.method == 'GET': From 4b0ed9dcd3c653618dc8176988c4252c842b3725 Mon Sep 17 00:00:00 2001 From: Hironsan Date: Wed, 2 Feb 2022 11:15:51 +0900 Subject: [PATCH 3/3] Change the project list's frontend --- frontend/components/project/ProjectList.vue | 61 ++++++++++++++++--- frontend/domain/models/project/project.ts | 13 +++- .../models/project/projectRepository.ts | 5 +- frontend/pages/projects/index.vue | 26 ++++++-- .../project/apiProjectRepository.ts | 10 +-- .../project/projectApplicationService.ts | 10 +-- .../application/project/projectData.ts | 16 ++++- 7 files changed, 114 insertions(+), 27 deletions(-) diff --git a/frontend/components/project/ProjectList.vue b/frontend/components/project/ProjectList.vue index 0cc4e9a2..7e129cbd 100644 --- a/frontend/components/project/ProjectList.vue +++ b/frontend/components/project/ProjectList.vue @@ -3,12 +3,15 @@ :value="value" :headers="headers" :items="items" + :options.sync="options" + :server-items-length="total" :search="search" :loading="isLoading" :loading-text="$t('generic.loading')" :no-data-text="$t('vuetify.noDataAvailable')" :footer-props="{ 'showFirstLastPage': true, + 'items-per-page-options': [10, 50, 100], 'items-per-page-text': $t('vuetify.itemsPerPageText'), 'page-text': $t('dataset.pageText') }" @@ -36,19 +39,21 @@ diff --git a/frontend/domain/models/project/project.ts b/frontend/domain/models/project/project.ts index 36ccef5e..dc0eedcc 100644 --- a/frontend/domain/models/project/project.ts +++ b/frontend/domain/models/project/project.ts @@ -1,4 +1,5 @@ -import { Expose } from 'class-transformer' +import "reflect-metadata" +import { Expose, Type } from 'class-transformer' export type ProjectType = 'DocumentClassification' | 'SequenceLabeling' | 'Seq2seq' | 'IntentDetectionAndSlotFilling' | 'ImageClassification' | 'Speech2text' @@ -74,6 +75,16 @@ export class ProjectReadItem { } } +export class ProjectItemList { + count: number; + next: string | null; + prev: string | null; + + @Type(() => ProjectReadItem) + @Expose({ name: 'results' }) + items: ProjectReadItem[]; +} + export class ProjectWriteItem { constructor( public id: number, diff --git a/frontend/domain/models/project/projectRepository.ts b/frontend/domain/models/project/projectRepository.ts index ebba0e17..223ea017 100644 --- a/frontend/domain/models/project/projectRepository.ts +++ b/frontend/domain/models/project/projectRepository.ts @@ -1,8 +1,9 @@ -import { ProjectReadItem, ProjectWriteItem } from '~/domain/models/project/project' +import { ProjectReadItem, ProjectWriteItem, ProjectItemList } from '~/domain/models/project/project' +export type SearchOption = {[key: string]: string | (string | null)[]} export interface ProjectRepository { - list(): Promise + list({ limit, offset, q }: SearchOption): Promise findById(id: string): Promise diff --git a/frontend/pages/projects/index.vue b/frontend/pages/projects/index.vue index 24e0fe83..4b7c99ce 100644 --- a/frontend/pages/projects/index.vue +++ b/frontend/pages/projects/index.vue @@ -33,17 +33,20 @@ + :total="projects.count" + @update:query="updateQuery" + /> diff --git a/frontend/repositories/project/apiProjectRepository.ts b/frontend/repositories/project/apiProjectRepository.ts index b9ad2e24..6da0810f 100644 --- a/frontend/repositories/project/apiProjectRepository.ts +++ b/frontend/repositories/project/apiProjectRepository.ts @@ -1,7 +1,7 @@ import { plainToInstance } from 'class-transformer' import ApiService from '@/services/api.service' -import { ProjectRepository } from '@/domain/models/project/projectRepository' -import { ProjectReadItem, ProjectWriteItem } from '~/domain/models/project/project' +import { ProjectRepository, SearchOption } from '@/domain/models/project/projectRepository' +import { ProjectReadItem, ProjectWriteItem, ProjectItemList } from '~/domain/models/project/project' export class APIProjectRepository implements ProjectRepository { @@ -9,10 +9,10 @@ export class APIProjectRepository implements ProjectRepository { private readonly request = ApiService ) {} - async list(): Promise { - const url = `/projects` + async list({ limit = '10', offset = '0', q = '' }: SearchOption): Promise { + const url = `/projects?limit=${limit}&offset=${offset}&q=${q}` const response = await this.request.get(url) - return response.data.map((item: any) => plainToInstance(ProjectReadItem, item)) + return plainToInstance(ProjectItemList, response.data) } async findById(id: string): Promise { diff --git a/frontend/services/application/project/projectApplicationService.ts b/frontend/services/application/project/projectApplicationService.ts index 74adebad..0f490995 100644 --- a/frontend/services/application/project/projectApplicationService.ts +++ b/frontend/services/application/project/projectApplicationService.ts @@ -1,5 +1,5 @@ -import { ProjectDTO, ProjectWriteDTO } from './projectData' -import { ProjectRepository } from '~/domain/models/project/projectRepository' +import { ProjectDTO, ProjectWriteDTO, ProjectListDTO } from './projectData' +import { ProjectRepository, SearchOption } from '~/domain/models/project/projectRepository' import { ProjectWriteItem } from '~/domain/models/project/project' @@ -8,10 +8,10 @@ export class ProjectApplicationService { private readonly repository: ProjectRepository ) {} - public async list(): Promise { + public async list(options: SearchOption): Promise { try { - const items = await this.repository.list() - return items.map(item => new ProjectDTO(item)) + const items = await this.repository.list(options) + return new ProjectListDTO(items) } catch(e: any) { throw new Error(e.response.data.detail) } diff --git a/frontend/services/application/project/projectData.ts b/frontend/services/application/project/projectData.ts index dd6ba5d8..e1800cea 100644 --- a/frontend/services/application/project/projectData.ts +++ b/frontend/services/application/project/projectData.ts @@ -1,4 +1,4 @@ -import { ProjectReadItem, ProjectType } from '~/domain/models/project/project' +import { ProjectReadItem, ProjectType, ProjectItemList } from '~/domain/models/project/project' export class ProjectDTO { id: number @@ -45,3 +45,17 @@ export class ProjectDTO { } export type ProjectWriteDTO = Pick + +export class ProjectListDTO { + count: number + next : string | null + prev : string | null + items: ProjectDTO[] + + constructor(item: ProjectItemList) { + this.count = item.count + this.next = item.next + this.prev = item.prev + this.items = item.items.map(_ => new ProjectDTO(_)) + } +}