From adc4f277366252b6b78c051a299c858c0d01d727 Mon Sep 17 00:00:00 2001 From: Hironsan Date: Mon, 5 Sep 2022 09:10:27 +0900 Subject: [PATCH] Enable to order projects by name, created_at, created_by, and project_type --- backend/projects/views/project.py | 2 +- frontend/components/project/ProjectList.vue | 29 ++++++++++--------- .../models/project/projectRepository.ts | 23 +++++++++++++-- .../project/apiProjectRepository.ts | 14 +++++++-- .../project/projectApplicationService.ts | 9 +++--- .../application/project/projectData.ts | 8 +++++ 6 files changed, 62 insertions(+), 23 deletions(-) diff --git a/backend/projects/views/project.py b/backend/projects/views/project.py index be71839a..976665a1 100644 --- a/backend/projects/views/project.py +++ b/backend/projects/views/project.py @@ -13,7 +13,7 @@ class ProjectList(generics.ListCreateAPIView): serializer_class = ProjectPolymorphicSerializer filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) search_fields = ("name", "description") - ordering_fields = "__all__" + ordering_fields = ["name", "created_at", "created_by", "project_type"] ordering = ["-created_at"] def get_permissions(self): diff --git a/frontend/components/project/ProjectList.vue b/frontend/components/project/ProjectList.vue index 4f21186c..d5481530 100644 --- a/frontend/components/project/ProjectList.vue +++ b/frontend/components/project/ProjectList.vue @@ -88,14 +88,14 @@ export default Vue.extend({ }, computed: { - headers() { + headers(): { text: any; value: string; sortable?: boolean }[] { return [ { text: this.$t('generic.name'), value: 'name' }, - { text: this.$t('generic.description'), value: 'description' }, + { text: this.$t('generic.description'), value: 'description', sortable: false }, { text: this.$t('generic.type'), value: 'projectType' }, { text: 'Created', value: 'createdAt' }, { text: 'Author', value: 'author' }, - { text: 'Tags', value: 'tags' } + { text: 'Tags', value: 'tags', sortable: false } ] } }, @@ -103,32 +103,35 @@ export default Vue.extend({ watch: { options: { handler() { - const self: any = this - self.updateQuery({ + this.updateQuery({ query: { - limit: self.options.itemsPerPage.toString(), - offset: ((self.options.page - 1) * self.options.itemsPerPage).toString(), - q: self.search + limit: this.options.itemsPerPage.toString(), + offset: ((this.options.page - 1) * this.options.itemsPerPage).toString(), + q: this.search } }) }, deep: true }, search() { - const self: any = this - self.updateQuery({ + this.updateQuery({ query: { - limit: self.options.itemsPerPage.toString(), + limit: this.options.itemsPerPage.toString(), offset: '0', - q: self.search + q: this.search } }) - self.options.page = 1 + this.options.page = 1 } }, methods: { updateQuery(payload: any) { + const { sortBy, sortDesc } = this.options + if (sortBy.length === 1 && sortDesc.length === 1) { + payload.query.sortBy = sortBy[0] + payload.query.sortDesc = sortDesc[0] + } this.$emit('update:query', payload) } } diff --git a/frontend/domain/models/project/projectRepository.ts b/frontend/domain/models/project/projectRepository.ts index 874579f6..4a46cb4a 100644 --- a/frontend/domain/models/project/projectRepository.ts +++ b/frontend/domain/models/project/projectRepository.ts @@ -1,9 +1,28 @@ import { ProjectReadItem, ProjectWriteItem, ProjectItemList } from '~/domain/models/project/project' -export type SearchOption = { [key: string]: string | (string | null)[] } +const sortableFieldList = ['name', 'projectType', 'createdAt', 'author'] as const +type SortableFields = typeof sortableFieldList[number] + +export class SearchQuery { + readonly limit: number = 10 + readonly offset: number = 0 + readonly q: string = '' + readonly sortBy: SortableFields = 'createdAt' + readonly sortDesc: boolean = false + + constructor(_limit: string, _offset: string, _q?: string, _sortBy?: string, _sortDesc?: string) { + this.limit = /^\d+$/.test(_limit) ? parseInt(_limit) : 10 + this.offset = /^\d+$/.test(_offset) ? parseInt(_offset) : 0 + this.q = _q || '' + this.sortBy = ( + _sortBy && sortableFieldList.includes(_sortBy as SortableFields) ? _sortBy : 'createdAt' + ) as SortableFields + this.sortDesc = _sortDesc === 'true' + } +} export interface ProjectRepository { - list({ limit, offset, q }: SearchOption): Promise + list(query: SearchQuery): Promise findById(id: string): Promise diff --git a/frontend/repositories/project/apiProjectRepository.ts b/frontend/repositories/project/apiProjectRepository.ts index 2d5486f0..22a20832 100644 --- a/frontend/repositories/project/apiProjectRepository.ts +++ b/frontend/repositories/project/apiProjectRepository.ts @@ -1,5 +1,5 @@ import ApiService from '@/services/api.service' -import { ProjectRepository, SearchOption } from '@/domain/models/project/projectRepository' +import { ProjectRepository, SearchQuery } from '@/domain/models/project/projectRepository' import { ProjectReadItem, ProjectWriteItem, ProjectItemList } from '@/domain/models/project/project' function toModel(item: { [key: string]: any }): ProjectReadItem { @@ -50,8 +50,16 @@ function toPayload(item: ProjectWriteItem): { [key: string]: any } { export class APIProjectRepository implements ProjectRepository { constructor(private readonly request = ApiService) {} - async list({ limit = '10', offset = '0', q = '' }: SearchOption): Promise { - const url = `/projects?limit=${limit}&offset=${offset}&q=${q}` + async list(query: SearchQuery): Promise { + const fieldMapper = { + name: 'name', + createdAt: 'created_at', + projectType: 'project_type', + author: 'created_by' + } + const sortBy = fieldMapper[query.sortBy] + const ordering = query.sortDesc ? `-${sortBy}` : `${sortBy}` + const url = `/projects?limit=${query.limit}&offset=${query.offset}&q=${query.q}&ordering=${ordering}` const response = await this.request.get(url) return new ProjectItemList( response.data.count, diff --git a/frontend/services/application/project/projectApplicationService.ts b/frontend/services/application/project/projectApplicationService.ts index 51f9c2ef..f20f17de 100644 --- a/frontend/services/application/project/projectApplicationService.ts +++ b/frontend/services/application/project/projectApplicationService.ts @@ -1,13 +1,14 @@ -import { ProjectDTO, ProjectWriteDTO, ProjectListDTO } from './projectData' -import { ProjectRepository, SearchOption } from '~/domain/models/project/projectRepository' +import { ProjectDTO, ProjectWriteDTO, ProjectListDTO, SearchQueryData } from './projectData' +import { ProjectRepository, SearchQuery } from '~/domain/models/project/projectRepository' import { ProjectWriteItem } from '~/domain/models/project/project' export class ProjectApplicationService { constructor(private readonly repository: ProjectRepository) {} - public async list(options: SearchOption): Promise { + public async list(q: SearchQueryData): Promise { try { - const items = await this.repository.list(options) + const query = new SearchQuery(q.limit, q.offset, q.q, q.sortBy, q.sortDesc) + const items = await this.repository.list(query) 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 49f8d006..c40e2dc8 100644 --- a/frontend/services/application/project/projectData.ts +++ b/frontend/services/application/project/projectData.ts @@ -80,3 +80,11 @@ export class ProjectListDTO { this.items = item.items.map((_) => new ProjectDTO(_)) } } + +export interface SearchQueryData { + limit: string + offset: string + q?: string + sortBy?: string + sortDesc?: string +}