Skip to content

Commit c0e219e

Browse files
committed
implementar preferencias de privacidad de perfil y valoraciones
- Añadir vista condicionada del perfil según configuración de privacidad - Mostrar mensaje de "Perfil privado" cuando un usuario oculta su perfil - Mantener visibles las valoraciones recibidas incluso con perfil privado - Implementar opción para ocultar historial de viajes - Adaptar las plantillas de perfil y valoraciones para respetar preferencias de privacidad
1 parent f781235 commit c0e219e

7 files changed

Lines changed: 208 additions & 49 deletions

File tree

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,12 @@ ehthumbs.db
5353
Thumbs.db
5454
.coverage
5555
documentacion/documents.md
56+
components_table.html
57+
api_components_table.html
58+
diagramas_modelos.html
59+
requirements.txt
60+
diagramas/components_files_table.html
61+
diagramas/chat_message_sequence.html
62+
diagramas/arquitectura_proyecto.html
63+
diagramas/api_components_table.html
64+
codigo/chat/mermaid_diagram.py

codigo/accounts/templates/accounts/user_profile.html

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,54 @@
11
{% extends 'base.html' %}
22
{% load static %}
33

4-
{% block title %}Perfil de {{ user.username }}{% endblock %}
4+
{% block title %}Perfil - {{ user.username }}{% endblock %}
55

66
{% block extra_styles %}
77
<link rel="stylesheet" href="{% static 'css/pages/profile.css' %}">
88
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
99
<style>
10+
.private-message {
11+
background-color: rgba(0, 0, 0, 0.05);
12+
border-radius: 12px;
13+
padding: 2rem;
14+
text-align: center;
15+
margin: 2rem 0;
16+
}
17+
18+
.private-message i {
19+
display: block;
20+
font-size: 3rem;
21+
color: var(--text-secondary);
22+
margin-bottom: 1rem;
23+
}
24+
25+
.private-message h3 {
26+
color: var(--text-primary);
27+
margin-bottom: 0.75rem;
28+
font-weight: 600;
29+
}
30+
31+
.private-message p {
32+
color: var(--text-secondary);
33+
margin-bottom: 1.5rem;
34+
}
35+
36+
.limited-profile {
37+
background-color: var(--card-background);
38+
border-radius: 12px;
39+
padding: 2rem;
40+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
41+
text-align: center;
42+
max-width: 500px;
43+
margin: 3rem auto;
44+
}
45+
46+
.limited-profile .profile-avatar {
47+
width: 100px;
48+
height: 100px;
49+
margin: 0 auto 1.5rem;
50+
}
51+
1052
/* Estilos específicos para la biografía */
1153
.user-bio {
1254
margin-top: 2.5rem;
@@ -45,6 +87,38 @@
4587
{% endblock %}
4688

4789
{% block content %}
90+
{% if profile_not_visible %}
91+
<div class="limited-profile">
92+
<div class="profile-avatar-container">
93+
<div class="profile-avatar">
94+
{{ user.username|first|upper }}
95+
</div>
96+
</div>
97+
<h1 class="profile-title">{{ user.username }}</h1>
98+
<p class="profile-subtitle">Miembro desde {{ user.date_joined|date:"F Y" }}</p>
99+
100+
<div class="private-message">
101+
<i class="fas fa-lock"></i>
102+
<h3>Perfil Privado</h3>
103+
<p>Este usuario ha configurado su perfil como privado.</p>
104+
105+
<div class="profile-actions">
106+
<a href="{% url 'reviews:list' %}?user={{ user.username }}" class="button button-secondary">
107+
<i class="fas fa-star"></i> Ver Valoraciones
108+
</a>
109+
<a href="{% url 'reports:create_report' %}?user_id={{ user.id }}" class="button button-secondary">
110+
<i class="fas fa-flag"></i> Reportar
111+
</a>
112+
<a href="{% url 'chat:create_direct_chat' user.id %}" class="button button-secondary">
113+
<i class="fas fa-envelope"></i> Enviar Mensaje
114+
</a>
115+
<a href="javascript:history.back()" class="button button-secondary">
116+
<i class="fas fa-arrow-left"></i> Volver
117+
</a>
118+
</div>
119+
</div>
120+
</div>
121+
{% else %}
48122
<div class="profile-container">
49123
<div class="profile-layout">
50124
<div class="profile-sidebar">
@@ -104,6 +178,13 @@ <h3>Biografía</h3>
104178

105179
<!-- Panel derecho: Viajes -->
106180
<div class="profile-content">
181+
{% if not show_rides_history %}
182+
<div class="private-message">
183+
<i class="fas fa-eye-slash"></i>
184+
<h3>Historial de Viajes Privado</h3>
185+
<p>Este usuario ha configurado su historial de viajes como privado.</p>
186+
</div>
187+
{% else %}
107188
<div class="rides-tabs">
108189
<button class="tab-button active" data-tab="driver">Como Conductor</button>
109190
<button class="tab-button" data-tab="passenger">Como Pasajero</button>
@@ -218,9 +299,11 @@ <h3 class="ride-title">{{ ride.origin }} → {{ ride.destination }}</h3>
218299
{% endfor %}
219300
</div>
220301
</div>
302+
{% endif %}
221303
</div>
222304
</div>
223305
</div>
306+
{% endif %}
224307

225308
{% block extra_js %}
226309
<script>

codigo/accounts/views.py

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def register_view(request):
4848
profile.user = user
4949
profile.save()
5050

51-
# Crear cuentas de Stripe para el usuario
51+
5252
customer_id, account_id = associate_stripe_accounts_to_user(user)
5353
if customer_id:
5454
messages.success(request, "Tu cuenta de pagos ha sido configurada correctamente.")
@@ -73,58 +73,81 @@ def logout_view(request):
7373
@login_required
7474
def profile_view(request, username):
7575
user, user_profile = get_user_and_profile(request, username)
76-
#Necesario en caso de buscar el perfil de otro usuario (no necesariamente el mismo que esta logueado)
76+
7777
if not user:
7878
return redirect(RIDE_LIST_URL)
7979

80-
# Primero obtenemos los datos de viajes
80+
81+
is_own_profile = request.user == user
82+
if not is_own_profile and not user_profile.profile_visible:
83+
84+
context = {
85+
USER_KEY: user,
86+
USER_PROFILE_KEY: user_profile,
87+
IS_OWN_PROFILE_KEY: False,
88+
'profile_not_visible': True,
89+
}
90+
return render(request, USER_PROFILE_TEMPLATE, context)
91+
92+
8193
rides_data = get_user_rides_data(user)
8294
profile_data = get_user_profile_data(user)
8395

84-
# Extraemos las listas de viajes de rides_data
85-
active_rides_as_driver = rides_data.get('active_rides_as_driver', [])
86-
expired_rides_as_driver = rides_data.get('expired_rides_as_driver', [])
87-
active_rides_as_passenger = rides_data.get('active_rides_as_passenger', [])
88-
expired_rides_as_passenger = rides_data.get('expired_rides_as_passenger', [])
89-
90-
# Ahora creamos los paginadores con las listas obtenidas
91-
# Paginación para viajes como conductor (activos)
92-
paginator_driver_active = Paginator(active_rides_as_driver, 6)
93-
driver_active_page = request.GET.get('page') if request.GET.get('tab') == 'driver' and request.GET.get('status') == 'active' else 1
94-
active_driver_page_obj = paginator_driver_active.get_page(driver_active_page)
95-
96-
# Paginación para viajes como conductor (finalizados)
97-
paginator_driver_expired = Paginator(expired_rides_as_driver, 6)
98-
driver_expired_page = request.GET.get('page') if request.GET.get('tab') == 'driver' and request.GET.get('status') == 'expired' else 1
99-
expired_driver_page_obj = paginator_driver_expired.get_page(driver_expired_page)
100-
101-
# Paginación para viajes como pasajero (activos)
102-
paginator_passenger_active = Paginator(active_rides_as_passenger, 6)
103-
passenger_active_page = request.GET.get('page') if request.GET.get('tab') == 'passenger' and request.GET.get('status') == 'active' else 1
104-
active_passenger_page_obj = paginator_passenger_active.get_page(passenger_active_page)
105-
106-
# Paginación para viajes como pasajero (finalizados)
107-
paginator_passenger_expired = Paginator(expired_rides_as_passenger, 6)
108-
passenger_expired_page = request.GET.get('page') if request.GET.get('tab') == 'passenger' and request.GET.get('status') == 'expired' else 1
109-
expired_passenger_page_obj = paginator_passenger_expired.get_page(passenger_expired_page)
110-
111-
# También necesitamos modificar el contexto para mostrar las versiones paginadas
96+
97+
show_rides_history = is_own_profile or user_profile.show_rides_history
98+
99+
100+
if show_rides_history:
101+
active_rides_as_driver = rides_data.get('active_rides_as_driver', [])
102+
expired_rides_as_driver = rides_data.get('expired_rides_as_driver', [])
103+
active_rides_as_passenger = rides_data.get('active_rides_as_passenger', [])
104+
expired_rides_as_passenger = rides_data.get('expired_rides_as_passenger', [])
105+
106+
107+
108+
paginator_driver_active = Paginator(active_rides_as_driver, 6)
109+
driver_active_page = request.GET.get('page') if request.GET.get('tab') == 'driver' and request.GET.get('status') == 'active' else 1
110+
active_driver_page_obj = paginator_driver_active.get_page(driver_active_page)
111+
112+
113+
paginator_driver_expired = Paginator(expired_rides_as_driver, 6)
114+
driver_expired_page = request.GET.get('page') if request.GET.get('tab') == 'driver' and request.GET.get('status') == 'expired' else 1
115+
expired_driver_page_obj = paginator_driver_expired.get_page(driver_expired_page)
116+
117+
118+
paginator_passenger_active = Paginator(active_rides_as_passenger, 6)
119+
passenger_active_page = request.GET.get('page') if request.GET.get('tab') == 'passenger' and request.GET.get('status') == 'active' else 1
120+
active_passenger_page_obj = paginator_passenger_active.get_page(passenger_active_page)
121+
122+
123+
paginator_passenger_expired = Paginator(expired_rides_as_passenger, 6)
124+
passenger_expired_page = request.GET.get('page') if request.GET.get('tab') == 'passenger' and request.GET.get('status') == 'expired' else 1
125+
expired_passenger_page_obj = paginator_passenger_expired.get_page(passenger_expired_page)
126+
else:
127+
128+
active_driver_page_obj = []
129+
expired_driver_page_obj = []
130+
active_passenger_page_obj = []
131+
expired_passenger_page_obj = []
132+
133+
112134
context = {
113135
USER_KEY: user,
114136
USER_PROFILE_KEY: user_profile,
115-
IS_OWN_PROFILE_KEY: request.user == user,
116-
# Incluimos los datos originales para mantener la compatibilidad con código existente
137+
IS_OWN_PROFILE_KEY: is_own_profile,
138+
'show_rides_history': show_rides_history,
139+
117140
**rides_data,
118141
**profile_data,
119-
# Añadimos los objetos de paginación
142+
120143
'active_driver_page_obj': active_driver_page_obj,
121144
'expired_driver_page_obj': expired_driver_page_obj,
122145
'active_passenger_page_obj': active_passenger_page_obj,
123146
'expired_passenger_page_obj': expired_passenger_page_obj,
124147
}
125148

126-
# Seleccionamos la plantilla adecuada según si el usuario está viendo su propio perfil u otro
127-
template = PROFILE_TEMPLATE if request.user == user else USER_PROFILE_TEMPLATE
149+
150+
template = PROFILE_TEMPLATE if is_own_profile else USER_PROFILE_TEMPLATE
128151

129152
return render(request, template, context)
130153

codigo/chat/consumers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from accounts.public import update_last_activity
1010
from .models import Chat
11-
from .constants import CHAT_GROUP_FORMAT, MESSAGE_TYPE_CHAT, MESSAGE_NO_PERMISSION
11+
from .constants import CHAT_GROUP_FORMAT, MESSAGE_TYPE_CHAT, MESSAGE_NO_PERMISSION, ERROR_PROCESSING_MESSAGE
1212
from ._utils import can_access_chat, format_message_for_api, save_chat_message, format_message_for_websocket
1313

1414
class ChatConsumer(AsyncWebsocketConsumer):

codigo/reviews/_utils.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
get_reviews_by_user, get_reviews_received_by_user,
2222
get_user_already_reviewed, ride_has_finished,
2323
user_has_participation)
24+
from accounts.public import get_user_profile
2425

2526

2627
def get_review_or_404(review_id):
@@ -41,17 +42,17 @@ def check_review_permission(request, ride):
4142
"""
4243
Verifica que el usuario tenga permiso para valorar un viaje.
4344
"""
44-
# Verificar que el usuario participó en el viaje (como conductor o pasajero)
45+
4546
if not user_has_participation(request.user, ride):
4647
messages.error(request, NO_PARTICIPATION_ERROR)
4748
return redirect("rides:ride_detail", ride_id=ride.id)
4849

49-
# Verificar que el viaje ya ha ocurrido
50+
5051
if not ride_has_finished(ride):
5152
messages.error(request, RIDE_NOT_FINISHED_ERROR)
5253
return redirect("rides:ride_detail", ride_id=ride.id)
5354

54-
# Verificar si el usuario ya ha dejado una valoración para este viaje
55+
5556
if get_user_already_reviewed(request.user, ride):
5657
messages.info(request, REVIEW_ALREADY_EXISTS_ERROR)
5758
return redirect("rides:ride_detail", ride_id=ride.id)
@@ -105,7 +106,7 @@ def prepare_review_list_context(request):
105106
"""
106107
Prepara el contexto para la lista de valoraciones.
107108
"""
108-
# Verificar si se ha solicitado ver valoraciones de otro usuario
109+
109110
username = request.GET.get("user")
110111
target_user = None
111112

@@ -117,21 +118,33 @@ def prepare_review_list_context(request):
117118
except User.DoesNotExist:
118119
pass
119120

120-
# Si no se encontró el usuario o no se proporcionó username, usar el usuario actual
121+
121122
if not target_user:
122123
target_user = request.user
123124

124-
# Determinar si estamos viendo nuestras propias valoraciones o las de otro usuario
125+
125126
viewing_own = target_user == request.user
126-
127-
reviews_given = get_reviews_by_user(target_user)
127+
128+
129+
target_profile = get_user_profile(target_user)
130+
131+
132+
profile_is_private = not viewing_own and not target_profile.profile_visible
133+
134+
128135
reviews_received = get_reviews_received_by_user(target_user)
136+
137+
138+
reviews_given = []
139+
if viewing_own or not profile_is_private:
140+
reviews_given = get_reviews_by_user(target_user)
129141

130142
return {
131143
REVIEWS_GIVEN_KEY: reviews_given,
132144
REVIEWS_RECEIVED_KEY: reviews_received,
133145
"target_user": target_user,
134146
"viewing_own": viewing_own,
147+
"profile_is_private": profile_is_private
135148
}
136149

137150

@@ -153,9 +166,9 @@ def redirect_after_delete(review):
153166
"""
154167
Redirecciona adecuadamente después de eliminar una valoración.
155168
"""
156-
# Si el viaje existe, redireccionar a la página de detalles del viaje
169+
157170
if review.ride:
158171
return redirect("rides:ride_detail", ride_id=review.ride.id)
159172

160-
# De lo contrario, redireccionar a la lista de valoraciones
173+
161174
return redirect(get_url_full(LIST_REVIEWS_NAME))

0 commit comments

Comments
 (0)