From 3a4d4d75000bd8550fc59c34d98c86920f365895 Mon Sep 17 00:00:00 2001 From: lucasgalheto Date: Sun, 15 Jun 2025 11:19:37 +0100 Subject: [PATCH 1/2] Melhoria Editar User + Mensagens de erro personalizadas --- backend/users/serializers.py | 9 ++ frontend/pages/users/_id.vue | 83 +++++++++++++++++-- .../user/UserApplicationService.ts | 34 +++++++- 3 files changed, 117 insertions(+), 9 deletions(-) diff --git a/backend/users/serializers.py b/backend/users/serializers.py index 06ced543..209e6e53 100644 --- a/backend/users/serializers.py +++ b/backend/users/serializers.py @@ -41,6 +41,15 @@ class UserDetailSerializer(serializers.ModelSerializer): 'name': group.name } return groups_dict + + def validate_email(self, value): + """ + Validate that the email is unique, excluding the current user instance. + """ + user_id = self.instance.id if self.instance else None + if User.objects.filter(email=value).exclude(id=user_id).exists(): + raise serializers.ValidationError("Este email já está sendo usado por outro usuário. Por favor, escolha um email diferente.") + return value class RegisterSerializer(serializers.ModelSerializer): diff --git a/frontend/pages/users/_id.vue b/frontend/pages/users/_id.vue index 8741ff61..acf39b62 100644 --- a/frontend/pages/users/_id.vue +++ b/frontend/pages/users/_id.vue @@ -46,11 +46,36 @@ -

Edit User:

+

Editar Usuário:

- - - + + + + + + + + + {{ $t('group.groups') || 'Groups' }} - Delete User + Excluir Usuário - Update Profile + Atualizar Perfil
@@ -167,6 +192,9 @@ export default Vue.extend({ user: null as UserDetails | null, editedUser: { username: '', + firstName: '', + lastName: '', + email: '', isStaff: false, isSuperUser: false, groups: [] as number[], @@ -203,6 +231,9 @@ export default Vue.extend({ // Save the user data to editedUser this.editedUser = { username: this.user.username, + firstName: this.user.firstName, + lastName: this.user.lastName, + email: this.user.email, isStaff: this.user.isStaff, isSuperUser: this.user.isSuperUser, groups: this.user.groups || [], @@ -256,6 +287,39 @@ export default Vue.extend({ return `Group ${groupId}` }, + translateErrorMessage(errorMessage: string): string { + // Traduções de mensagens de erro comuns + const translations: { [key: string]: string } = { + 'A user with this email already exists.': 'Este email já está sendo usado por outro usuário. Por favor, escolha um email diferente.', + 'This field may not be blank.': 'Este campo não pode estar vazio.', + 'Enter a valid email address.': 'Digite um endereço de email válido.', + 'A user with that username already exists.': 'Este nome de usuário já está sendo usado. Por favor, escolha outro nome de usuário.', + 'Username: A user with that username already exists.': 'Nome de usuário: Este nome já está sendo usado. Por favor, escolha outro nome de usuário.', + 'Email: A user with this email already exists.': 'Email: Este email já está sendo usado por outro usuário. Por favor, escolha um email diferente.', + } + + // Verifica se há uma tradução exata + if (translations[errorMessage]) { + return translations[errorMessage] + } + + // Verifica se a mensagem já está em português (nossa validação do backend) + if (errorMessage.includes('Este email já está sendo usado')) { + return errorMessage + } + + // Para mensagens que começam com "Email:", traduz apenas a parte específica + if (errorMessage.startsWith('Email:')) { + const emailError = errorMessage.replace('Email: ', '') + if (translations[`Email: ${emailError}`]) { + return translations[`Email: ${emailError}`] + } + return `Email: ${emailError}` + } + + return errorMessage + }, + async updateUser() { try { this.loading = true @@ -266,16 +330,19 @@ export default Vue.extend({ const userService = new UserApplicationService(new APIUserRepository()) await userService.updateUser(id, { username: this.editedUser.username, + first_name: this.editedUser.firstName, + last_name: this.editedUser.lastName, + email: this.editedUser.email, is_staff: this.editedUser.isStaff, is_superuser: this.editedUser.isSuperUser, groups: this.editedUser.groups // Using the array of IDs }) - this.successMessage = 'User profile updated successfully' + this.successMessage = 'Perfil do usuário atualizado com sucesso!' await this.fetchUser() await this.fetchGroups() // Re-fetch groups to update the selection } catch (error: any) { - this.errorMessage = error.message + this.errorMessage = this.translateErrorMessage(error.message) } finally { this.loading = false } diff --git a/frontend/services/application/user/UserApplicationService.ts b/frontend/services/application/user/UserApplicationService.ts index 854c359d..2d195de7 100644 --- a/frontend/services/application/user/UserApplicationService.ts +++ b/frontend/services/application/user/UserApplicationService.ts @@ -40,7 +40,39 @@ export class UserApplicationService { try { return await this.repository.updateUser(id, data) } catch (e: any) { - throw new Error(e.response?.data?.detail || `Failed to update user with ID ${id}`) + // Handle specific validation errors + if (e.response?.data) { + const errorData = e.response.data + + // Check for field-specific errors + if (errorData.email && Array.isArray(errorData.email)) { + throw new Error(`Email: ${errorData.email[0]}`) + } + + if (errorData.username && Array.isArray(errorData.username)) { + throw new Error(`Username: ${errorData.username[0]}`) + } + + if (errorData.first_name && Array.isArray(errorData.first_name)) { + throw new Error(`First Name: ${errorData.first_name[0]}`) + } + + if (errorData.last_name && Array.isArray(errorData.last_name)) { + throw new Error(`Last Name: ${errorData.last_name[0]}`) + } + + // Check for general detail message + if (errorData.detail) { + throw new Error(errorData.detail) + } + + // Check for non_field_errors + if (errorData.non_field_errors && Array.isArray(errorData.non_field_errors)) { + throw new Error(errorData.non_field_errors[0]) + } + } + + throw new Error(`Failed to update user with ID ${id}`) } } From 150793542d6a0ef35821ebbb977cec6171183196 Mon Sep 17 00:00:00 2001 From: yasminddias Date: Sun, 15 Jun 2025 11:23:23 +0100 Subject: [PATCH 2/2] Deleta perspectiva --- backend/projects/perspective/views.py | 36 +++- .../components/perspective/QuestionList.vue | 12 ++ frontend/i18n/en/projects/perspectives.js | 9 +- .../pages/projects/_id/perspectives/index.vue | 167 +++++++++++++++++- .../perspective/apiPerspectiveRepository.ts | 13 +- .../perspectiveApplicationService.ts | 4 + test_delete_all.html | 127 +++++++++++++ 7 files changed, 350 insertions(+), 18 deletions(-) create mode 100644 test_delete_all.html diff --git a/backend/projects/perspective/views.py b/backend/projects/perspective/views.py index bb52060d..52ce17ab 100644 --- a/backend/projects/perspective/views.py +++ b/backend/projects/perspective/views.py @@ -24,7 +24,7 @@ class QuestionViewSet(viewsets.ModelViewSet): ordering = ['order', 'created_at'] def get_permissions(self): - if self.action in ['create', 'update', 'partial_update', 'destroy', 'bulk_create', 'bulk_delete']: + if self.action in ['create', 'update', 'partial_update', 'destroy', 'bulk_create', 'bulk_delete', 'delete_all']: permission_classes = [IsAuthenticated, CanCreatePerspective] else: permission_classes = [IsAuthenticated, CanViewPerspective] @@ -90,6 +90,15 @@ class QuestionViewSet(viewsets.ModelViewSet): return super().list(request, *args, **kwargs) + def destroy(self, request, *args, **kwargs): + """Override destroy to ensure consistent response format""" + instance = self.get_object() + question_text = instance.text[:50] + "..." if len(instance.text) > 50 else instance.text + self.perform_destroy(instance) + return Response({ + 'message': f'Question "{question_text}" deleted successfully' + }, status=status.HTTP_200_OK) + def perform_create(self, serializer): import logging logger = logging.getLogger(__name__) @@ -122,23 +131,36 @@ class QuestionViewSet(viewsets.ModelViewSet): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @action(detail=False, methods=['delete']) + @action(detail=False, methods=['post']) def bulk_delete(self, request, project_id=None): question_ids = request.data.get('ids', []) if not question_ids: return Response({'error': 'No question IDs provided'}, status=status.HTTP_400_BAD_REQUEST) - + questions = Question.objects.filter( id__in=question_ids, project_id=project_id ) - + deleted_count = questions.count() questions.delete() - + + return Response({ + 'message': f'{deleted_count} questions deleted successfully', + 'deleted_count': deleted_count + }, status=status.HTTP_200_OK) + + @action(detail=False, methods=['post']) + def delete_all(self, request, project_id=None): + """Delete all questions in the project""" + questions = Question.objects.filter(project_id=project_id) + deleted_count = questions.count() + questions.delete() + return Response({ - 'message': f'{deleted_count} questions deleted successfully' - }, status=status.HTTP_204_NO_CONTENT) + 'message': f'{deleted_count} questions deleted successfully', + 'deleted_count': deleted_count + }, status=status.HTTP_200_OK) class AnswerViewSet(viewsets.ModelViewSet): diff --git a/frontend/components/perspective/QuestionList.vue b/frontend/components/perspective/QuestionList.vue index 78539f61..080d03f7 100644 --- a/frontend/components/perspective/QuestionList.vue +++ b/frontend/components/perspective/QuestionList.vue @@ -1,5 +1,17 @@