Project for the UPV to develop an app like BlaBlaCar but only for UPV people.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

request for join journey

+411 -89
+1
requirements/base.txt
··· 10 10 sphinx==1.4.1 11 11 sphinx-rtd-theme==0.1.9 12 12 sphinxcontrib-httpdomain==1.4.0 13 + Pillow==3.2.0
+4 -1
upvcarshare/config/router.py
··· 5 5 from rest_framework.routers import SimpleRouter 6 6 7 7 from journeys.api.v1.resources import TransportResource, ResidenceResource, CampusResource, JourneyResource, \ 8 - join_journey, leave_journey, recommended_journeys, cancel_journey, journey_messages, MessageResource 8 + join_journey, leave_journey, recommended_journeys, cancel_journey, journey_messages, MessageResource, \ 9 + confirm_journey, reject_journey 9 10 from notifications.api.v1.resources import NotificationResource 10 11 from users.api.v1.resources import me 11 12 ··· 23 24 url(r'^journeys/(?P<pk>[^/.]+)/cancel/$', cancel_journey, name='cancel-journeys'), 24 25 url(r'^journeys/(?P<pk>[^/.]+)/recommended/$', recommended_journeys, name='recommended-journeys'), 25 26 url(r'^journeys/(?P<pk>[^/.]+)/join/$', join_journey, name='join-journey'), 27 + url(r'^journeys/(?P<pk>[^/.]+)/confirm/$', confirm_journey, name='confirm-journey'), 28 + url(r'^journeys/(?P<pk>[^/.]+)/reject/$', reject_journey, name='reject-journey'), 26 29 url(r'^journeys/(?P<pk>[^/.]+)/leave/$', leave_journey, name='leave-journey'), 27 30 url(r'^journeys/(?P<pk>[^/.]+)/messages/$', journey_messages, name='journey-messages'), 28 31 url(r'^users/me/$', me, kwargs={'pk': 'me'}),
+1
upvcarshare/config/settings/base.py
··· 123 123 'django.contrib.messages.context_processors.messages', 124 124 'django.template.context_processors.request', 125 125 'notifications.context_processors.notifications', 126 + 'journeys.context_processors.passenger_statuses', 126 127 ], 127 128 'loaders': [ 128 129 'django.template.loaders.filesystem.Loader',
+39
upvcarshare/core/files.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals 3 + 4 + import hashlib 5 + import os 6 + import random 7 + import time 8 + 9 + from django.utils.deconstruct import deconstructible 10 + from django.utils.text import slugify 11 + 12 + 13 + @deconstructible 14 + class UploadToDir(object): 15 + """Generates a function to give to ``upload_to`` parameter in 16 + models.Fields, that generates an name for uploaded files based on ``populate_from`` 17 + attribute. 18 + """ 19 + 20 + def __init__(self, path, populate_from=None, random_name=False): 21 + self.path = path 22 + self.populate_from = populate_from 23 + self.random_name = random_name 24 + 25 + def __call__(self, instance, filename): 26 + """Generates an name for an uploaded file.""" 27 + if self.populate_from is not None and not hasattr(instance, self.populate_from): 28 + raise AttributeError("Instance hasn't {} attribute".format(self.populate_from)) 29 + ext = filename.split('.')[-1] 30 + readable_name = slugify(filename.split('.')[0]) 31 + if self.populate_from: 32 + readable_name = slugify(getattr(instance, self.populate_from)) 33 + if self.random_name: 34 + random_name = hashlib.sha256( 35 + "{}--{}".format(time.time(), random.random()).encode('utf-8') 36 + ) 37 + readable_name = random_name.hexdigest() 38 + file_name = "{}.{}".format(readable_name, ext) 39 + return os.path.join(self.path, file_name)
+55
upvcarshare/journeys/api/v1/resources.py
··· 12 12 MessageSerializer 13 13 from journeys.exceptions import UserNotAllowed 14 14 from journeys.models import Transport, Residence, Campus, Journey, Message 15 + from users.models import User 15 16 16 17 17 18 class TransportResource(viewsets.ModelViewSet): ··· 91 92 return Response(status=status.HTTP_201_CREATED) 92 93 93 94 join_journey = JoinJourneyResource.as_view({"post": "join"}) 95 + 96 + 97 + class ConfirmJourneyResource(viewsets.ViewSet): 98 + """Resource to allow journey creator to confirm a passenger. 99 + 100 + Example: 101 + POST /api/v1/journeys/1/confirm/ 102 + """ 103 + permission_classes = [ 104 + IsAuthenticated, 105 + ] 106 + 107 + @staticmethod 108 + def confirm(request, **kwargs): 109 + pk = kwargs.get('pk', 0) 110 + journey = get_object_or_404(Journey, pk=pk) 111 + if journey.user != request.user: 112 + raise PermissionDenied 113 + try: 114 + user_pk = request.data.get("user") 115 + user = User.objects.get(pk=user_pk) 116 + except User.DoesNotExist: 117 + raise NotFound 118 + journey.confirm_passenger(user) 119 + return Response(status=status.HTTP_201_CREATED) 120 + 121 + confirm_journey = ConfirmJourneyResource.as_view({"post": "confirm"}) 122 + 123 + 124 + class RejectJourneyResource(viewsets.ViewSet): 125 + """Resource to allow journey creator to reject a passenger. 126 + 127 + Example: 128 + POST /api/v1/journeys/1/reject/ 129 + """ 130 + permission_classes = [ 131 + IsAuthenticated, 132 + ] 133 + 134 + @staticmethod 135 + def reject(request, **kwargs): 136 + pk = kwargs.get('pk', 0) 137 + journey = get_object_or_404(Journey, pk=pk) 138 + if journey.user != request.user: 139 + raise PermissionDenied 140 + try: 141 + user_pk = request.data.get("user") 142 + user = User.objects.get(pk=user_pk) 143 + except User.DoesNotExist: 144 + raise NotFound 145 + journey.reject_passenger(user) 146 + return Response(status=status.HTTP_201_CREATED) 147 + 148 + reject_journey = ConfirmJourneyResource.as_view({"post": "reject"}) 94 149 95 150 96 151 class LeaveJourneyResource(viewsets.ViewSet):
+12
upvcarshare/journeys/context_processors.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from journeys import CONFIRMED, REJECTED, UNKNOWN 5 + 6 + 7 + def passenger_statuses(request): 8 + return { 9 + "CONFIRMED": CONFIRMED, 10 + "REJECTED": REJECTED, 11 + "UNKNOWN": UNKNOWN, 12 + }
+13
upvcarshare/journeys/forms.py
··· 187 187 """ 188 188 pass 189 189 190 + 191 + class ConfirmRejectJourneyForm(forms.Form): 192 + """Form to get the user to confirm or reject.""" 193 + 194 + user = forms.IntegerField(widget=forms.HiddenInput()) 195 + 196 + def clean_user(self): 197 + user_pk = self.cleaned_data["user"] 198 + try: 199 + user = User.objects.get(pk=user_pk) 200 + except User.DoesNotExist: 201 + raise forms.ValidationError(_("El usuario no existe")) 202 + return user
+29 -5
upvcarshare/journeys/models.py
··· 15 15 16 16 from core.models import GisTimeStampedModel 17 17 from journeys import JOURNEY_KINDS, GOING, RETURN, DEFAULT_DISTANCE, DEFAULT_PROJECTED_SRID, DEFAULT_WGS84_SRID, \ 18 - DEFAULT_TIME_WINDOW, PASSENGER_STATUSES, UNKNOWN 18 + DEFAULT_TIME_WINDOW, PASSENGER_STATUSES, UNKNOWN, CONFIRMED, REJECTED 19 19 from journeys.exceptions import NoFreePlaces, NotAPassenger, AlreadyAPassenger 20 20 from journeys.helpers import make_point_wgs84 21 21 from journeys.managers import JourneyManager, ResidenceManager, MessageManager 22 - from notifications import JOIN, LEAVE, CANCEL 22 + from notifications import JOIN, LEAVE, CANCEL, CONFIRM, REJECT 23 23 from notifications.decorators import dispatch 24 24 25 25 ··· 171 171 172 172 def count_passengers(self): 173 173 """Gets the count of passengers.""" 174 - return self.passengers.count() 174 + return self.passengers.filter(status=CONFIRMED).count() 175 175 176 176 def current_free_places(self): 177 177 """Gets the current number of free places.""" ··· 189 189 if self.count_passengers() < self.free_places: 190 190 return Passenger.objects.create( 191 191 journey=self, 192 - user=user 192 + user=user, 193 + status=UNKNOWN 193 194 ) 194 195 raise NoFreePlaces() 195 196 ··· 202 203 raise NotAPassenger() 203 204 self.passengers.filter(user=user).delete() 204 205 205 - def is_passenger(self, user): 206 + @dispatch(CONFIRM) 207 + def confirm_passenger(self, user): 208 + """Confirms the user as passenger.""" 209 + if not self.is_passenger(user=user, all_passengers=True): 210 + raise NotAPassenger() 211 + self.passengers.filter(user=user).update(status=CONFIRMED) 212 + 213 + @dispatch(REJECT) 214 + def reject_passenger(self, user): 215 + """Confirms the user as passenger.""" 216 + if not self.is_passenger(user=user, all_passengers=True): 217 + raise NotAPassenger() 218 + self.passengers.filter(user=user).update(status=REJECTED) 219 + 220 + def is_passenger(self, user, all_passengers=False): 206 221 """Checks if the given user is a passenger of this journey.""" 222 + if not all_passengers: 223 + return self.passengers.filter(user=user, status=CONFIRMED).exists() 207 224 return self.passengers.filter(user=user).exists() 208 225 209 226 def recommended(self, ignore_full=False): ··· 246 263 self.disabled = True 247 264 self.save() 248 265 266 + def passengers_list(self, user): 267 + """Gets the suitable list of passenger for this user.""" 268 + if self.user == user: 269 + return self.passengers.all() 270 + elif self.is_passenger(user): 271 + return self.passengers.filter(status=CONFIRMED) 272 + return Passenger.objects.none() 249 273 250 274 class Passenger(TimeStampedModel): 251 275 """A user who has joined a journey."""
+2
upvcarshare/journeys/templatetags/journeys_tags.py
··· 9 9 @register.inclusion_tag('journeys/templatetags/item.html', takes_context=True) 10 10 def journey_item(context, journey): 11 11 """Renders a journey as an item list.""" 12 + request = context["request"] 12 13 context["journey_item"] = journey 14 + context["passenger"] = journey.passengers.filter(user=request.user).first() 13 15 return context 14 16 15 17
+18
upvcarshare/journeys/tests/test_api.py
··· 105 105 self.client.force_authenticate(user=user) 106 106 response = self.client.post(url) 107 107 self.assertEqual(response.status_code, status.HTTP_201_CREATED) 108 + self.assertEquals(0, journey.count_passengers()) 109 + 110 + def test_join_and_confirm_journey(self): 111 + user = UserFactory() 112 + journey = self._make_journey(GOING) 113 + url = "/api/v1/journeys/{}/join/".format(journey.pk) 114 + self.client.force_authenticate(user=user) 115 + response = self.client.post(url) 116 + self.assertEqual(response.status_code, status.HTTP_201_CREATED) 117 + self.assertEquals(0, journey.count_passengers()) 118 + url = "/api/v1/journeys/{}/confirm/".format(journey.pk) 119 + data = { 120 + "user": user.pk 121 + } 122 + self.client.force_authenticate(user=journey.user) 123 + response = self.client.post(url, data=data) 124 + self.assertEqual(response.status_code, status.HTTP_201_CREATED) 108 125 self.assertEquals(1, journey.count_passengers()) 109 126 110 127 def test_leave_journey(self): 111 128 user = UserFactory() 112 129 journey = self._make_journey(GOING) 113 130 journey.join_passenger(user) 131 + journey.confirm_passenger(user) 114 132 self.assertEquals(1, journey.count_passengers()) 115 133 url = "/api/v1/journeys/{}/leave/".format(journey.pk) 116 134 self.client.force_authenticate(user=user)
+9
upvcarshare/journeys/tests/test_models.py
··· 71 71 user = UserFactory() 72 72 journey = self._make_journey() 73 73 self.assertIsInstance(journey.join_passenger(user), Passenger) 74 + self.assertFalse(journey.is_passenger(user)) 75 + self.assertEquals(journey.count_passengers(), 0) 76 + 77 + def test_join_and_confirm_passenger(self): 78 + user = UserFactory() 79 + journey = self._make_journey() 80 + self.assertIsInstance(journey.join_passenger(user), Passenger) 81 + journey.confirm_passenger(user) 74 82 self.assertTrue(journey.is_passenger(user)) 75 83 self.assertEquals(journey.count_passengers(), 1) 76 84 ··· 95 103 user = UserFactory() 96 104 journey = self._make_journey() 97 105 journey.join_passenger(user) 106 + journey.confirm_passenger(user) 98 107 journey.leave_passenger(user) 99 108 self.assertFalse(journey.is_passenger(user)) 100 109
+2 -3
upvcarshare/journeys/tests/test_views.py
··· 29 29 self.assertLoginRequired("journeys:create") 30 30 with self.login(self.user): 31 31 data = { 32 - "residence": ResidenceFactory(user=self.user).pk, 33 - "campus": CampusFactory().pk, 34 - "kind": GOING, 32 + "origin": "residence:%s" % ResidenceFactory(user=self.user).pk, 33 + "destiny": "campus:%s" % CampusFactory().pk, 35 34 "free_places": 4, 36 35 "time_window": 30, 37 36 "departure": (timezone.now() + datetime.timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S")
+4 -1
upvcarshare/journeys/urls.py
··· 5 5 6 6 from journeys.views import CreateJourneyView, CreateResidenceView, EditResidenceView, EditJourneyView, \ 7 7 RecommendedJourneyView, CurrentUserJourneyView, CurrentUserResidencesView, JoinJourneyView, LeaveJourneyView, \ 8 - JourneyView, PassengerJourneyView, ThrowOutPassengerView, DeleteResidence, CancelJourneyView 8 + JourneyView, PassengerJourneyView, ThrowOutPassengerView, DeleteResidence, CancelJourneyView, ConfirmJourneyView, \ 9 + RejectJourneyView 9 10 10 11 urlpatterns = [ 11 12 # Residences ··· 23 24 url(r"(?P<pk>\d+)/cancel/$", CancelJourneyView.as_view(), name="cancel"), 24 25 url(r"(?P<pk>\d+)/join/$", JoinJourneyView.as_view(), name="join"), 25 26 url(r"(?P<pk>\d+)/leave/$", LeaveJourneyView.as_view(), name="leave"), 27 + url(r"(?P<pk>\d+)/confirm/$", ConfirmJourneyView.as_view(), name="confirm"), 28 + url(r"(?P<pk>\d+)/reject/$", RejectJourneyView.as_view(), name="reject"), 26 29 url(r"(?P<pk>\d+)/throw-out/$", ThrowOutPassengerView.as_view(), name="throw-out"), 27 30 url(r"(?P<pk>\d+)/$", JourneyView.as_view(), name="details"), 28 31
+78 -6
upvcarshare/journeys/views.py
··· 11 11 12 12 from journeys import GOING 13 13 from journeys.exceptions import AlreadyAPassenger, NoFreePlaces, NotAPassenger 14 - from journeys.forms import JourneyForm, ResidenceForm, FilterForm, CancelJourneyForm, SmartJourneyForm 14 + from journeys.forms import JourneyForm, ResidenceForm, FilterForm, CancelJourneyForm, SmartJourneyForm, \ 15 + ConfirmRejectJourneyForm 15 16 from journeys.models import Journey, Residence, Campus, Passenger 16 17 17 18 ··· 137 138 138 139 template_name = "journeys/details.html" 139 140 141 + @staticmethod 142 + def show_passengers(request, journey): 143 + if journey.user == request.user: 144 + return True 145 + return journey.is_passenger(request.user) and journey.count_passengers() > 0 and not journey.needs_driver() 146 + 147 + @staticmethod 148 + def show_messenger(request, journey): 149 + if journey.user == request.user: 150 + return True 151 + return journey.is_passenger(request.user) 152 + 140 153 def get(self, request, pk): 141 154 journey = get_object_or_404(Journey, pk=pk) 142 155 data = { 143 156 "journey": journey, 144 - "show_passengers": not journey.needs_driver() and journey.count_passengers() > 0, 157 + "show_passengers": self.show_passengers(request, journey), 158 + "show_messenger": self.show_messenger(request, journey), 145 159 "is_fulfilled": journey.is_fulfilled(), 146 160 "fulfilled_by": journey.fulfilled_by(), 147 - "passengers": journey.passengers.all(), 161 + "passengers": journey.passengers_list(request.user), 148 162 "recommended": journey.recommended(), 149 163 } 150 164 return render(request, self.template_name, data) ··· 177 191 template_name = "journeys/user_list.html" 178 192 179 193 def get(self, request): 194 + now = timezone.now() 180 195 data = { 181 - "journeys": Journey.objects.filter(user=request.user).order_by("departure") 196 + "journeys": Journey.objects.filter( 197 + user=request.user, 198 + departure__gte=now 199 + ).order_by("departure") 182 200 } 183 201 return render(request, self.template_name, data) 184 202 ··· 214 232 journey = get_object_or_404(Journey, pk=pk) 215 233 try: 216 234 journey.join_passenger(request.user) 217 - messages.success(request, _('Te has unido al trayecto')) 235 + messages.success(request, _('Has solicitado unirte al trayecto')) 218 236 except AlreadyAPassenger: 219 - messages.error(request, _('¡Ya estás unido al trayecto!')) 237 + messages.error(request, _('¡Ya has solicitado unirte al trayecto!')) 220 238 except NoFreePlaces: 221 239 messages.error(request, _('No quedan plazas libres en el trayecto')) 222 240 return_to = request.POST.get("return_to", self.return_to) 223 241 return redirect(return_to) 224 242 225 243 244 + class ConfirmJourneyView(LoginRequiredMixin, View): 245 + """View to handle the action of joining a journey. """ 246 + return_to = "journeys:recommended" 247 + 248 + def post(self, request, pk): 249 + passenger = get_object_or_404(Passenger, pk=pk) 250 + if passenger.journey.user != request.user: 251 + raise Http404 252 + form = ConfirmRejectJourneyForm(request.POST) 253 + if form.is_valid(): 254 + user = form.cleaned_data["user"] 255 + try: 256 + passenger.journey.confirm_passenger(user) 257 + except NotAPassenger: 258 + messages.success(request, _('El usuario no está en este trayecto')) 259 + return_to = request.POST.get("return_to", self.return_to) 260 + return redirect(return_to) 261 + 262 + 263 + class RejectJourneyView(LoginRequiredMixin, View): 264 + """View to handle the action of joining a journey. """ 265 + return_to = "journeys:recommended" 266 + 267 + def post(self, request, pk): 268 + passenger = get_object_or_404(Passenger, pk=pk) 269 + if passenger.journey.user != request.user: 270 + raise Http404 271 + form = ConfirmRejectJourneyForm(request.POST) 272 + if form.is_valid(): 273 + user = form.cleaned_data["user"] 274 + try: 275 + passenger.journey.reject_passenger(user) 276 + except NotAPassenger: 277 + messages.success(request, _('El usuario no está en este trayecto')) 278 + return_to = request.POST.get("return_to", self.return_to) 279 + return redirect(return_to) 280 + 281 + 226 282 class LeaveJourneyView(LoginRequiredMixin, View): 227 283 """View to handle the action of leaving a journey. """ 228 284 return_to = "journeys:recommended" ··· 243 299 return_to = "journeys:recommended" 244 300 245 301 def post(self, request, pk): 302 + passenger = get_object_or_404(Passenger, pk=pk) 303 + if passenger.journey.user != request.user: 304 + raise Http404 305 + try: 306 + passenger.journey.leave_passenger(passenger.user) 307 + messages.success(request, _('Has expulsado al pasajero')) 308 + except NotAPassenger: 309 + messages.success(request, _('No puedes expulsar a este pasajero')) 310 + return_to = request.POST.get("return_to", self.return_to) 311 + return redirect(return_to) 312 + 313 + 314 + class AcceptPassengerView(LoginRequiredMixin, View): 315 + """View to accept a request of a possible passenger.""" 316 + def post(self, request, pk): 317 + 246 318 passenger = get_object_or_404(Passenger, pk=pk) 247 319 if passenger.journey.user != request.user: 248 320 raise Http404
+1 -1
upvcarshare/notifications/__init__.py
··· 2 2 from __future__ import unicode_literals, print_function, absolute_import 3 3 4 4 # Notification verbs 5 - JOIN, LEAVE, CANCEL, MESSAGE = "join", "leave", "cancel", "message" 5 + JOIN, LEAVE, CANCEL, MESSAGE, CONFIRM, REJECT = "join", "leave", "cancel", "message", "confirm", "reject"
+1
upvcarshare/notifications/tests/test_models.py
··· 33 33 user = UserFactory() 34 34 journey = self._make_journey(self.user) 35 35 journey.join_passenger(user) 36 + journey.confirm_passenger(user) 36 37 journey.leave_passenger(user) 37 38 self.assertEquals(1, Notification.objects.filter(user=self.user, verb=JOIN).count()) 38 39 self.assertEquals(1, Notification.objects.filter(user=self.user, verb=LEAVE).count())
+32
upvcarshare/templates/journeys/blocks/details.description.html
··· 1 + {% load i18n %} 2 + {% load journeys_tags %} 3 + 4 + {% if not journey.needs_driver %} 5 + <h3>{% trans "Datos del transporte" %}</h3> 6 + <table class="table"> 7 + <tbody> 8 + <tr> 9 + <th scope="row">{% trans "Conductor" %}</th> 10 + <td>{{ journey.driver.get_full_name }}</td> 11 + </tr> 12 + <tr> 13 + <th scope="row">{% trans "Plazas libres" %}</th> 14 + <td>{{ journey.current_free_places }}/{{ journey.free_places }}</td> 15 + </tr> 16 + </tbody> 17 + </table> 18 + {% elif is_fulfilled %} 19 + <h3>{% trans "Trayecto enlazado" %}</h3> 20 + <p class="text-muted">{% trans "Te has apuntado a estos trayectos de otros usuarios para cubrir tu necesidad." %}</p> 21 + {% for fulfilled_journey in fulfilled_by %} 22 + {% journey_item fulfilled_journey %} 23 + {% endfor %} 24 + {% else %} 25 + <h3>{% trans "Trayectos recomendados" %}</h3> 26 + <p class="text-muted">{% trans "Otros trayectos ofrecidos por otros usuarios que encajan en tus criterios." %}</p> 27 + {% for recommended_journey in recommended %} 28 + {% journey_item recommended_journey %} 29 + {% empty %} 30 + <h4 class="text-muted">{% trans "No se ha encontrado transporte recomendado" %}</h4> 31 + {% endfor %} 32 + {% endif %}
+14
upvcarshare/templates/journeys/blocks/details.location.html
··· 1 + {% load i18n %} 2 + {% load core_tags %} 3 + 4 + <h3> 5 + {% if journey.kind == 0 %} 6 + {% trans "Origen" %} 7 + {% else %} 8 + {% trans "Destino" %} 9 + {% endif %} 10 + </h3> 11 + <p>{{ journey.residence.address }}</p> 12 + <a href="{{ journey.residence.google_maps_link }}" target="_blank"> 13 + <img src="{% google_static_map journey.residence.get_position_wgs84 300 300 14 %}" class="img-fluid"> 14 + </a>
+41
upvcarshare/templates/journeys/blocks/details.passengers.html
··· 1 + {% load i18n %} 2 + 3 + <h3>{% trans "Pasajeros" %}</h3> 4 + <table class="table"> 5 + <tbody> 6 + {% for passenger in passengers %} 7 + <td> 8 + {{ passenger.user.get_full_name }} <br /> 9 + <small>{% trans "Unido el" %} {{ passenger.created }}</small> 10 + </td> 11 + {% if request.user == journey.driver and request.user == journey.user %} 12 + {% if passenger.status == CONFIRMED %} 13 + <td> 14 + <form method="post" action="{% url "journeys:throw-out" passenger.pk %}"> 15 + {% csrf_token %} 16 + <input type="hidden" name="return_to" value="{{ request.path }}"> 17 + <button type="submit" class="btn btn-danger">{% trans "Expulsar" %}</button> 18 + </form> 19 + </td> 20 + {% else %} 21 + <td> 22 + <form method="post" action="{% url "journeys:confirm" passenger.pk %}"> 23 + {% csrf_token %} 24 + <input type="hidden" name="return_to" value="{{ request.path }}"> 25 + <input type="hidden" name="user" value="{{ passenger.user.pk }}"> 26 + <button type="submit" class="btn btn-success">{% trans "Confirmar" %}</button> 27 + </form> 28 + </td> 29 + <td> 30 + <form method="post" action="{% url "journeys:reject" passenger.pk %}"> 31 + {% csrf_token %} 32 + <input type="hidden" name="return_to" value="{{ request.path }}"> 33 + <input type="hidden" name="user" value="{{ passenger.user.pk }}"> 34 + <button type="submit" class="btn btn-danger">{% trans "Rechazar" %}</button> 35 + </form> 36 + </td> 37 + {% endif %} 38 + {% endif %} 39 + {% endfor %} 40 + </tbody> 41 + </table>
+7 -64
upvcarshare/templates/journeys/details.html
··· 1 - {% extends "sidebar.html" %} 1 + {% extends "header.html" %} 2 2 {% load i18n %} 3 3 {% load core_tags %} 4 4 {% load journeys_tags %} ··· 28 28 {% block content %} 29 29 <div class="row"> 30 30 <div class="col-sm-8"> 31 - {% if not journey.needs_driver %} 32 - <h3>{% trans "Datos del transporte" %}</h3> 33 - <table class="table"> 34 - <tbody> 35 - <tr> 36 - <th scope="row">{% trans "Conductor" %}</th> 37 - <td>{{ journey.driver.get_full_name }}</td> 38 - </tr> 39 - <tr> 40 - <th scope="row">{% trans "Plazas libres" %}</th> 41 - <td>{{ journey.current_free_places }}/{{ journey.free_places }}</td> 42 - </tr> 43 - </tbody> 44 - </table> 45 - {% elif is_fulfilled %} 46 - <h3>{% trans "Trayecto enlazado" %}</h3> 47 - <p class="text-muted">{% trans "Te has apuntado a estos trayectos de otros usuarios para cubrir tu necesidad." %}</p> 48 - {% for fulfilled_journey in fulfilled_by %} 49 - {% journey_item fulfilled_journey %} 50 - {% endfor %} 51 - {% else %} 52 - <h3>{% trans "Trayectos recomendados" %}</h3> 53 - <p class="text-muted">{% trans "Otros trayectos ofrecidos por otros usuarios que encajan en tus criterios." %}</p> 54 - {% for recommended_journey in recommended %} 55 - {% journey_item recommended_journey %} 56 - {% empty %} 57 - <h4 class="text-muted">{% trans "No se ha encontrado transporte recomendado" %}</h4> 58 - {% endfor %} 31 + {% include "journeys/blocks/details.description.html" %} 32 + {% if show_messenger %} 33 + <h3>{% trans "Tablón de mensajes" %}</h3> 34 + <messenger journey="{{ journey.pk }}" first-name="{{ request.user.first_name }}" last-name="{{ request.user.last_name }}"></messenger> 59 35 {% endif %} 60 - <h3>{% trans "Tablón de mensajes" %}</h3> 61 - <messenger journey="{{ journey.pk }}" first-name="{{ request.user.first_name }}" last-name="{{ request.user.last_name }}"></messenger> 62 36 </div> 63 37 <div class="col-sm-4"> 64 38 {% if show_passengers %} 65 - <h3>{% trans "Pasajeros" %}</h3> 66 - <table class="table"> 67 - <tbody> 68 - {% for passenger in passengers %} 69 - <td> 70 - {{ passenger.user.get_full_name }} 71 - </td> 72 - <td> 73 - {% trans "Unido el" %} {{ passenger.created }} 74 - </td> 75 - {% if request.user == journey.driver and request.user == journey.user %} 76 - <td> 77 - <form method="post" action="{% url "journeys:throw-out" passenger.pk %}"> 78 - {% csrf_token %} 79 - <input type="hidden" name="return_to" value="{{ request.path }}"> 80 - <button type="submit" class="btn btn-danger">{% trans "Expulsar" %}</button> 81 - </form> 82 - </td> 83 - {% endif %} 84 - {% endfor %} 85 - </tbody> 86 - </table> 39 + {% include "journeys/blocks/details.passengers.html" %} 87 40 {% endif %} 88 - <h3> 89 - {% if journey.kind == 0 %} 90 - {% trans "Origen" %} 91 - {% else %} 92 - {% trans "Destino" %} 93 - {% endif %} 94 - </h3> 95 - <p>{{ journey.residence.address }}</p> 96 - <a href="{{ journey.residence.google_maps_link }}" target="_blank"> 97 - <img src="{% google_static_map journey.residence.get_position_wgs84 300 300 14 %}" class="img-fluid"> 98 - </a> 41 + {% include "journeys/blocks/details.location.html" %} 99 42 </div> 100 43 </div> 101 44 {% endblock content %}
+3 -3
upvcarshare/templates/journeys/templatetags/join_leave_button.html
··· 4 4 <form method="post" action="{% url "journeys:join" journey_item.pk %}"> 5 5 {% csrf_token %} 6 6 <input type="hidden" name="return_to" value="{{ request.path }}"> 7 - {% if not journey_item.disabled %} 8 - <button type="submit" class="btn btn-success">{% trans "Unirse" %}</button> 9 - {% else %} 7 + {% if journey_item.disabled or passenger.status == UNKNOWN or passenger.status == REJECTED %} 10 8 <button type="button" disabled="disabled" class="btn btn-secondary">{% trans "Unirse" %}</button> 9 + {% else %} 10 + <button type="submit" class="btn btn-success">{% trans "Unirse" %}</button> 11 11 {% endif %} 12 12 13 13 </form>
+1 -1
upvcarshare/templates/users/edit.html
··· 17 17 {% block content %} 18 18 <div class="row"> 19 19 <div class="col-xs-12"> 20 - <form method="post" action=""> 20 + <form method="post" action="" enctype="multipart/form-data"> 21 21 {% csrf_token %} 22 22 {% for field in form %} 23 23 <div class="form-group row{% if field.errors %} has-danger{% endif %}">
+1 -1
upvcarshare/users/forms.py
··· 61 61 62 62 class Meta: 63 63 model = User 64 - fields = ["first_name", "last_name", "email", "default_address", "default_position", "default_distance"] 64 + fields = ["first_name", "last_name", "email", "avatar", "default_address", "default_position", "default_distance"] 65 65 widgets = { 66 66 "first_name": forms.TextInput(attrs={"class": "form-control"}), 67 67 "last_name": forms.TextInput(attrs={"class": "form-control"}),
+32
upvcarshare/users/migrations/0002_auto_20160619_1804.py
··· 1 + # -*- coding: utf-8 -*- 2 + # Generated by Django 1.9.7 on 2016-06-19 18:04 3 + from __future__ import unicode_literals 4 + 5 + import core.files 6 + import django.contrib.gis.db.models.fields 7 + from django.db import migrations, models 8 + 9 + 10 + class Migration(migrations.Migration): 11 + 12 + dependencies = [ 13 + ('users', '0001_initial'), 14 + ] 15 + 16 + operations = [ 17 + migrations.AddField( 18 + model_name='user', 19 + name='avatar', 20 + field=models.ImageField(blank=True, default='avatars/default.png', max_length=255, null=True, upload_to=core.files.UploadToDir('avatars/', random_name=True)), 21 + ), 22 + migrations.AlterField( 23 + model_name='user', 24 + name='default_address', 25 + field=models.TextField(blank=True, help_text='Dirección que por defecto se usará para crear trayectos', null=True, verbose_name='dirección por defecto'), 26 + ), 27 + migrations.AlterField( 28 + model_name='user', 29 + name='default_distance', 30 + field=models.PositiveIntegerField(blank=True, default=500, help_text='Distancia máxima que se utilizará para encontrar trayectos (metros)', null=True, verbose_name='distancia por defecto'), 31 + ), 32 + ]
+7
upvcarshare/users/models.py
··· 9 9 from django.utils.translation import ugettext_lazy as _ 10 10 from rest_framework.authtoken.models import Token 11 11 12 + from core.files import UploadToDir 12 13 from journeys import DEFAULT_PROJECTED_SRID, DEFAULT_DISTANCE, DEFAULT_WGS84_SRID 13 14 14 15 15 16 @python_2_unicode_compatible 16 17 class User(AbstractUser): 17 18 """Custom user model.""" 19 + avatar = models.ImageField( 20 + upload_to=UploadToDir('avatars/', random_name=True), default="avatars/default.png", 21 + null=True, blank=True, 22 + max_length=255 23 + ) 24 + 18 25 default_address = models.TextField( 19 26 verbose_name=_("dirección por defecto"), 20 27 help_text=_("Dirección que por defecto se usará para crear trayectos"),
+1 -1
upvcarshare/users/tests/test_views.py
··· 55 55 "last_name": "bar" 56 56 } 57 57 response = self.post(url_name, data=data) 58 - self.response_200(response=response) 58 + self.response_302(response=response) 59 59 user = User.objects.get(pk=self.user.pk) 60 60 self.assertEquals(data["first_name"], user.first_name) 61 61 self.assertEquals(data["last_name"], user.last_name)
+3 -2
upvcarshare/users/views.py
··· 3 3 4 4 from braces.views import CsrfExemptMixin 5 5 from braces.views import LoginRequiredMixin 6 + from django.contrib import messages 6 7 from django.contrib.auth import authenticate, login, logout 7 8 from django.core.urlresolvers import reverse 8 9 from django.shortcuts import render, redirect ··· 58 59 return render(request, self.template_name, data) 59 60 60 61 def post(self, request): 61 - form = UserForm(request.POST, instance=request.user) 62 + form = UserForm(request.POST, request.FILES, instance=request.user) 62 63 data = { 63 64 "form": form 64 65 } 65 - print(form.errors) 66 66 if form.is_valid(): 67 67 form.save() 68 + return redirect(reverse("users:edit")) 68 69 return render(request, self.template_name, data) 69 70 70 71