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.

now using journey templates to create recurrence

+159 -74
+13
upvcarshare/core/templatetags/core_tags.py
··· 1 1 # -*- coding: utf-8 -*- 2 2 from __future__ import unicode_literals, print_function, absolute_import 3 3 4 + import pytz 5 + import six 6 + from dateutil.parser import parse 4 7 from django import template 5 8 from django.conf import settings 6 9 from django.http import QueryDict 10 + from django.template.defaultfilters import date 11 + from django.utils import timezone 12 + from django.utils.timezone import make_aware 7 13 8 14 from journeys import DEFAULT_GOOGLE_MAPS_SRID 9 15 from journeys.helpers import make_point ··· 33 39 request = context["request"] 34 40 names = names.split(",") 35 41 return _class if request.resolver_match.view_name in names else "" 42 + 43 + 44 + @register.filter(expects_localtime=True, is_safe=False) 45 + def smart_date(value): 46 + if isinstance(value, six.text_type): 47 + value = parse(value, dayfirst=True, ignoretz=True) 48 + return value
+18 -18
upvcarshare/journeys/forms.py
··· 6 6 import floppyforms 7 7 import pytz 8 8 import re 9 - from dateutil.rrule import rrulestr 10 9 from django import forms 11 10 from django.contrib.gis.geos import GEOSGeometry 12 11 from django.core.exceptions import ObjectDoesNotExist ··· 36 35 fields = ["name", "address", "position", "distance"] 37 36 widgets = { 38 37 "name": forms.TextInput(attrs={"class": "form-control"}), 39 - "address": forms.Textarea(attrs={"class": "form-control"}), 38 + "address": forms.HiddenInput(attrs={"class": "form-control", "ng-value": "address"}), 40 39 } 41 40 42 41 def clean_position(self): ··· 64 63 65 64 class JourneyForm(forms.ModelForm): 66 65 67 - i_am_driver = forms.BooleanField( 68 - label=_("¿Eres conductor?"), 69 - required=False, 70 - initial=False, 71 - widget=forms.RadioSelect( 72 - choices=((True, _('Sí')), (False, _('No'))), 73 - ) 74 - ) 75 - 76 66 class Meta: 77 67 model = Journey 78 68 fields = ["free_places", "departure", "arrival"] ··· 120 110 121 111 class SmartJourneyTemplateForm(forms.ModelForm): 122 112 123 - origin = forms.CharField(widget=forms.HiddenInput()) 124 - destiny = forms.CharField(widget=forms.HiddenInput()) 113 + origin = forms.CharField(widget=forms.HiddenInput(), required=False) 114 + destiny = forms.CharField(widget=forms.HiddenInput(), required=False) 125 115 126 116 i_am_driver = forms.BooleanField( 127 117 label=_("¿Soy conductor?*"), ··· 137 127 ) 138 128 139 129 free_places = forms.IntegerField( 140 - label=_("Cuántas plazas libres tengo"), 130 + label=_("Plazas libres"), 141 131 required=False, 142 - widget=forms.NumberInput(attrs={"class": "form-control"}) 132 + widget=forms.NumberInput(attrs={"class": "form-control"}), 133 + help_text=_("Dejar en blanco para usar el valor por defecto del transporte seleccionado") 143 134 ) 144 135 145 136 class Meta: ··· 170 161 171 162 def clean_origin(self): 172 163 origin = self.cleaned_data["origin"] 164 + if not origin: 165 + raise forms.ValidationError(_("El origen obligatorio")) 173 166 data = origin.split(":") 174 167 models = {"residence": Residence, "campus": Campus} 175 168 try: ··· 179 172 180 173 def clean_destiny(self): 181 174 destiny = self.cleaned_data["destiny"] 175 + if not destiny: 176 + raise forms.ValidationError(_("El destino obligatorio")) 182 177 data = destiny.split(":") 183 178 models = {"residence": Residence, "campus": Campus} 184 179 try: ··· 188 183 189 184 def clean_departure(self): 190 185 departure = self.cleaned_data["departure"] 186 + departure = timezone.localtime(departure, pytz.timezone("UTC")) 191 187 time_window = self.cleaned_data.get("time_window", 30) 192 188 now = timezone.now() 193 189 if departure < now: ··· 211 207 def clean_free_places(self): 212 208 free_places = self.cleaned_data["free_places"] 213 209 transport = self.cleaned_data["transport"] 210 + if not free_places and transport: 211 + free_places = transport.default_places 214 212 if transport is not None and free_places > transport.default_places: 215 213 raise forms.ValidationError(_("No puedes ofertar más plazas que las que tienes en el transporte")) 216 214 return free_places 217 215 218 216 def clean_recurrence(self): 219 - """Delete DTSTART from recurrence, we use departure field.""" 217 + """Delete bad data from recurrence.""" 220 218 recurrence = self.cleaned_data["recurrence"] 221 - return re.sub(r"DTSTART=.+;", "", recurrence) 219 + recurrence = re.sub(r"DTSTART=.+;", "", recurrence) 220 + recurrence = re.sub(r"BYSECOND=NAN", "", recurrence) 221 + return recurrence 222 222 223 223 def save(self, commit=True, **kwargs): 224 224 """When save a journey form, you have to provide an user.""" ··· 374 374 distance = self.cleaned_data["distance"] 375 375 departure_date = self.cleaned_data["departure_date"] 376 376 departure_time = self.cleaned_data["departure_time"] 377 - departure = make_aware(datetime.datetime.combine(departure_date, departure_time)) 377 + departure = make_aware(datetime.datetime.combine(departure_date, departure_time), timezone=pytz.timezone("UTC")) 378 378 search_by_time = self.cleaned_data.get("search_by_time", False) 379 379 time_window = self.cleaned_data["time_window"] 380 380 return Journey.objects.search(
+20
upvcarshare/journeys/migrations/0018_auto_20170625_2018.py
··· 1 + # -*- coding: utf-8 -*- 2 + # Generated by Django 1.10.2 on 2017-06-25 20:18 3 + from __future__ import unicode_literals 4 + 5 + from django.db import migrations, models 6 + 7 + 8 + class Migration(migrations.Migration): 9 + 10 + dependencies = [ 11 + ('journeys', '0017_auto_20170624_2019'), 12 + ] 13 + 14 + operations = [ 15 + migrations.AlterField( 16 + model_name='residence', 17 + name='address', 18 + field=models.TextField(blank=True, default='', help_text='La dirección del lugar, según quieras que la vean los demás.', verbose_name='dirección'), 19 + ), 20 + ]
+33 -20
upvcarshare/journeys/models.py
··· 82 82 here. Each residence belongs to a user. 83 83 """ 84 84 user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="residences") 85 - address = models.TextField(verbose_name=_("dirección"), help_text=_("La dirección del lugar, según quieras que la vean los demás.")) 85 + address = models.TextField( 86 + verbose_name=_("dirección"), 87 + help_text=_("La dirección del lugar, según quieras que la vean los demás."), 88 + default="", 89 + blank=True 90 + ) 86 91 87 92 objects = ResidenceManager() 88 93 ··· 194 199 rules = rrulestr(self.recurrence, dtstart=self.departure) 195 200 if rules._until is None: 196 201 rules._until = default_until(self.departure) 197 - dates = list(map(lambda d: make_aware(d), list(rules))) 202 + dates = list(rules) 203 + if dates and dates[0].tzinfo is None: 204 + dates = list(map(lambda d: make_aware(d), dates)) 198 205 return zip(dates, map(lambda d: d + interval, dates)) 199 206 return [(self.departure, self.arrival)] 200 207 ··· 311 318 return self.free_places - self.count_passengers() 312 319 return 0 313 320 321 + def _join_passenger(self, user): 322 + if self.passengers.filter(user=user).exists() or self.template.driver == user: 323 + raise AlreadyAPassenger() 324 + if self.count_passengers() < self.free_places: 325 + passenger = Passenger.objects.create( 326 + journey=self, 327 + user=user, 328 + status=UNKNOWN 329 + ) 330 + return passenger 331 + 314 332 @dispatch(JOIN) 315 333 def join_passenger(self, user, join_to=None): 316 334 """A user joins a journey. ··· 319 337 """ 320 338 # Join only one 321 339 if join_to is None or join_to == "one": 322 - if self.passengers.filter(user=user).exists() or self.template.driver == user: 323 - raise AlreadyAPassenger() 324 - if self.count_passengers() < self.free_places: 325 - passenger = Passenger.objects.create( 326 - journey=self, 327 - user=user, 328 - status=UNKNOWN 329 - ) 330 - return passenger 340 + return self._join_passenger(user=user) 331 341 # Join to recurrence 332 342 elif join_to is not None and join_to == "all": 333 343 if self.has_recurrence: 334 - journeys = self.brothers() 344 + journeys = self.brothers(exclude_myself=True) 335 345 journeys = journeys.filter(departure__gte=self.departure) 336 - passengers = [] 346 + passengers = [self._join_passenger(user=user)] 337 347 for journey in journeys: 338 348 try: 339 349 passengers.append(journey.join_passenger(user)) ··· 344 354 elif join_to is not None and len(join_to.split("/")) > 0: 345 355 dates = map(lambda item: datetime.datetime.strptime(item, "%d/%m/%Y"), join_to.split(",")) 346 356 conditions = [Q(departure__day=date.day, departure__month=date.month, departure__year=date.year) for date in dates] 347 - journeys = self.brothers() 357 + journeys = self.brothers(exclude_myself=True) 348 358 journeys = journeys.filter(reduce(lambda x, y: x | y, conditions)) 349 - passengers = [] 359 + passengers = [self._join_passenger(user=user)] 350 360 for journey in journeys: 351 361 try: 352 362 passengers.append(journey.join_passenger(user)) ··· 355 365 return passengers 356 366 raise NoFreePlaces() 357 367 358 - @dispatch(LEAVE) 359 - def leave_passenger(self, user): 360 - """A user leave a journey. 361 - :param user: 362 - """ 368 + def _leave_passenger(self, user): 363 369 if not self.is_passenger(user=user): 364 370 raise NotAPassenger() 365 371 self.passengers.filter(user=user).delete() ··· 367 373 if self.total_passengers < 0: 368 374 self.total_passengers = 0 369 375 self.save() 376 + 377 + @dispatch(LEAVE) 378 + def leave_passenger(self, user, leave_from=None): 379 + """A user leave a journey. 380 + :param user: 381 + """ 382 + self._leave_passenger(user=user) 370 383 371 384 @dispatch(THROW_OUT) 372 385 def throw_out(self, user):
+21 -10
upvcarshare/journeys/views/journeys.py
··· 10 10 from django.http import Http404 11 11 from django.shortcuts import render, redirect, get_object_or_404 12 12 from django.utils import timezone 13 + from django.utils.safestring import mark_safe 13 14 from django.utils.timezone import make_naive 14 15 from django.utils.translation import ugettext_lazy as _ 15 16 from django.views.generic import View ··· 27 28 template_name = "journeys/create.smart.html" 28 29 form = SmartJourneyTemplateForm 29 30 30 - def get(self, request): 31 + @staticmethod 32 + def _initial_values(request): 31 33 residences = Residence.objects.filter(user=request.user) 32 34 campuses = Campus.objects.all() 33 35 initial_departure = timezone.now().replace(second=0, minute=0) + datetime.timedelta(hours=1) ··· 38 40 "departure": initial_departure, 39 41 "arrival": initial_departure + datetime.timedelta(minutes=30) 40 42 } 43 + return initial 44 + 45 + def get(self, request): 46 + initial = self._initial_values(request) 41 47 form = self.form(initial=initial, user=request.user) 42 48 data = { 43 49 "form": form 44 50 } 45 51 # Warnings 46 52 if request.user.transports.count() == 0: 47 - messages.warning(request, _("Parece que no has creado ningún medio de transporte, " 48 - "si quieres crear un viaje y que otros compañeros puedan " 49 - "apuntarse, tienes que registrar antes un medio de transporte.")) 53 + messages.warning(request, mark_safe(_( 54 + "Parece que no has creado ningún medio de transporte, " 55 + "si quieres crear un viaje y que otros compañeros puedan " 56 + "apuntarse, tienes que <strong>registrar antes un medio de transporte</strong>." 57 + ))) 50 58 if request.user.residences.count() == 0: 51 - messages.warning(request, _("Parece que no has especificado desde donde o hasta donde sueles viajar. " 52 - "Para dar de alta un viaje deberás antes registrar al menos un lugar para usar " 53 - "como origen o destino.")) 59 + messages.warning(request, mark_safe(_( 60 + "Parece que no has especificado desde donde o hasta donde sueles viajar. " 61 + "Para dar de alta un viaje deberás antes <strong>registrar al menos un lugar</strong> para usar " 62 + "como origen o destino." 63 + ))) 54 64 return render(request, self.template_name, data) 55 65 56 66 def post(self, request): 57 - form = self.form(request.POST, user=request.user) 67 + form = self.form(request.POST, user=request.user, initial=self._initial_values(request)) 58 68 data = { 59 69 "form": form 60 70 } 61 71 if form.is_valid(): 62 - journey = form.save() 63 - return redirect("journeys:details", pk=journey.pk) 72 + journey_template = form.save() 73 + journeys = journey_template.journeys.order_by("-departure") 74 + return redirect("journeys:details", pk=journeys.first().pk) 64 75 return render(request, self.template_name, data) 65 76 66 77
+1 -1
upvcarshare/journeys/views/places.py
··· 36 36 if form.is_valid(): 37 37 residence = form.save(user=request.user) 38 38 messages.success(request, ugettext('Has creado el lugar "%s"') % residence.name) 39 - return redirect("journeys:residences") 39 + return redirect(request.GET.get("next", "journeys:residences")) 40 40 return render(request, self.template_name, data) 41 41 42 42
+1 -1
upvcarshare/journeys/views/transports.py
··· 38 38 if form.is_valid(): 39 39 form.save(user=request.user) 40 40 messages.success(request, _('Has creado el transporte correctamente')) 41 - return redirect("journeys:transports") 41 + return redirect(request.GET.get("next", "journeys:transports")) 42 42 data = {"form": form} 43 43 return render(request, self.template_name, data) 44 44
+1 -1
upvcarshare/notifications/models.py
··· 67 67 target_date = localize(target_date) 68 68 if self.verb == JOIN: 69 69 # actor is a user and target is a journey 70 - value = _("%(user)s se ha <strong>unido</strong> al viaje <strong>%(journey)s</strong> del %(date)s") % { 70 + value = _("%(user)s ha <strong>solicitado</strong> unirse al viaje <strong>%(journey)s</strong> del %(date)s") % { 71 71 "user": six.text_type(self.actor), 72 72 "journey": six.text_type(self.target).lower(), 73 73 "date": target_date,
+3 -1
upvcarshare/static/src/js/journeys/index.js
··· 15 15 JoinJourneyForm, 16 16 SearchJourneyForm, 17 17 ConfirmPassengerForm, 18 - RejectPassengerForm 18 + RejectPassengerForm, 19 + ResidenceForm 19 20 } from './journey.directive'; 20 21 import JoinAllOneController from './journeys.controller'; 21 22 ··· 50 51 .component('rRules', RRulesComponent) 51 52 52 53 .directive('journeyForm', JourneyForm) 54 + .directive('residenceForm', ResidenceForm) 53 55 .directive('searchJourneyForm', SearchJourneyForm) 54 56 .directive('joinJourneyForm', JoinJourneyForm) 55 57 .directive('confirmPassengerForm', ConfirmPassengerForm)
+11 -1
upvcarshare/static/src/js/journeys/journey.directive.js
··· 32 32 } 33 33 }); 34 34 35 + const ResidenceForm = () => ({ 36 + restrict: 'A', 37 + link: (scope, element, attr) => { 38 + scope.address = ""; 39 + scope.onUpdateAddress = (value) => { 40 + scope.address = value; 41 + } 42 + } 43 + }); 44 + 35 45 const JoinJourneyForm = ($uibModal) => ({ 36 46 restrict: 'A', 37 47 link: (scope, element, attr) => { ··· 143 153 144 154 145 155 export {JourneyForm, JoinJourneyForm, SearchJourneyForm, ConfirmPassengerForm, 146 - RejectPassengerForm}; 156 + RejectPassengerForm, ResidenceForm};
+4 -2
upvcarshare/static/src/js/journeys/journeys.component.js
··· 30 30 fieldName: '@', 31 31 fieldId: '@', 32 32 overrideValue: '<', 33 - onUpdate: '&' 33 + onUpdate: '&', 34 + master: '=' 34 35 } 35 36 }; 36 37 ··· 85 86 radiusFieldId: '@', 86 87 positionValue: '@', 87 88 positionField: '@', 88 - positionFieldId: '@' 89 + positionFieldId: '@', 90 + onUpdate: '&' 89 91 } 90 92 }; 91 93
+15 -10
upvcarshare/static/src/js/journeys/journeys.controller.js
··· 135 135 // Call to onUpdate when $ctrl.picker.date changes. 136 136 this.scope.$watch('$ctrl.picker.date', (previousValue, currentValue) => { 137 137 // console.log("Watcher:", previousValue, currentValue) 138 - if (currentValue !== undefined && previousValue !== currentValue) { 139 - this.onUpdate({"value": currentValue}); 140 - } 141 - if (currentValue == undefined && previousValue !== undefined) { 142 - this.onUpdate({"value": previousValue}); 138 + if (this.master) { 139 + if (currentValue !== undefined && previousValue !== currentValue) { 140 + this.onUpdate({"value": currentValue}); 141 + } 142 + if (currentValue == undefined && previousValue !== undefined) { 143 + this.onUpdate({"value": previousValue}); 144 + } 143 145 } 144 146 }); 145 147 } ··· 357 359 } 358 360 359 361 getInitialCenter() { 360 - var re = /POINT \(([0-9\-\.]+) ([0-9\-\.]+)\)/; 361 - var values = this.positionValue.match(re); 362 + const re = /POINT \(([0-9\-\.]+) ([0-9\-\.]+)\)/; 363 + const values = this.positionValue.match(re); 362 364 if (values.length == 3) { 363 365 return { 364 366 latitude: parseFloat(values[2]), ··· 425 427 control: {}, 426 428 events: { 427 429 center_changed: () => { 428 - var value = this.circle.center; 430 + const value = this.circle.center; 429 431 this.pointString = this.positionObjectToGis(value.latitude, value.longitude); 430 432 } 431 433 } ··· 435 437 template: 'searchbox.tpl.html', 436 438 events: { 437 439 places_changed: (searchBox) => { 438 - var places = searchBox.getPlaces(), 439 - place = {}; 440 + const places = searchBox.getPlaces(); 441 + let place = {}; 440 442 if (places.length > 0) { 441 443 place = places[0]; 442 444 this.changeCenter( 443 445 place.geometry.location.lat(), 444 446 place.geometry.location.lng() 445 447 ); 448 + if (this.onUpdate !== undefined) { 449 + this.onUpdate({value: place.formatted_address}) 450 + } 446 451 } 447 452 } 448 453 }
+4
upvcarshare/static/src/sass/project.scss
··· 72 72 background-color: $upv-dark-grey; 73 73 } 74 74 75 + b, strong { 76 + font-weight: bold; 77 + } 78 + 75 79 h1, .h1 { 76 80 color: $upv-red; font-weight: normal 77 81 }
+1 -1
upvcarshare/templates/header.html
··· 51 51 <li class="nav-item {% add_active_class "journeys:create" %} dropdown"> 52 52 <a class="nav-link" href="{% url "journeys:create" %}">{% trans "Crear Viaje" %}</a> 53 53 </li> 54 - <li class="nav-item {% add_active_class "users:edit,journeys:list,journeys:residences,journeys:transports,journeys:details,journeys:edit" %} dropdown"> 54 + <li class="nav-item {% add_active_class "users:edit,journeys:residences,journeys:transports,journeys:details,journeys:edit" %} dropdown"> 55 55 <a class="nav-link dropdown-toggle" 56 56 data-toggle="dropdown" 57 57 href="#"
+4 -4
upvcarshare/templates/journeys/create.smart.html
··· 1 1 {% extends "header.html" %} 2 - 2 + {% load core_tags %} 3 3 {% load i18n %} 4 4 5 5 {% block title %}{% trans "Crear viaje" %}{% endblock title %} ··· 55 55 </div> 56 56 <div class="col-sm-5"> 57 57 <div class="btn-group float-xs-right" role="group" aria-label="{% trans "Acciones" %}"> 58 - <a href="{% url "journeys:create-transport" %}" class="btn btn-outline-success">{% trans "Crear nuevo transporte" %}</a> 58 + <a href="{% url "journeys:create-transport" %}?next=/journeys/create/" class="btn btn-outline-success">{% trans "Crear nuevo transporte" %}</a> 59 59 </div> 60 60 </div> 61 61 </div> 62 62 {% elif field.name == "departure" %} 63 - <journey-datetime field-name="{{ field.name }}" field-id="{{ field.auto_id }}" value="{{ field.value|date:"c" }}" on-update="onUpdateDeparture(value)"></journey-datetime> 63 + <journey-datetime field-name="{{ field.name }}" field-id="{{ field.auto_id }}" value="{{ field.value|smart_date|date:"c" }}" on-update="onUpdateDeparture(value)" master="true"></journey-datetime> 64 64 <r-rules field-id="id_recurrence" field-name="recurrence" override-departure="newDepartureValue"></r-rules> 65 65 {% elif field.name == "arrival" %} 66 - <journey-datetime field-name="{{ field.name }}" field-id="{{ field.auto_id }}" value="{{ field.value|date:"c" }}" override-value="newArrivalValue"></journey-datetime> 66 + <journey-datetime field-name="{{ field.name }}" field-id="{{ field.auto_id }}" value="{{ field.value|smart_date|date:"c" }}" override-value="newArrivalValue"></journey-datetime> 67 67 {% elif field.name == "recurrence" %} 68 68 {% else %} 69 69 {{ field }}
+1 -1
upvcarshare/templates/partials/journeys/circle_map.html
··· 3 3 .angular-google-map-container { height: 300px; } 4 4 </style> 5 5 <script id="searchbox.tpl.html" type="text/ng-template"> 6 - <input type="text" class="form-control map-search-box" placeholder="{% trans "Buscar una dirección..." %}" prevent-submit> 6 + <input type="text" class="form-control map-search-box" placeholder="{% trans "Escribe la dirección" %}" prevent-submit> 7 7 </script> 8 8 <div id="map-canvas"> 9 9 <ui-gmap-google-map center="$ctrl.map.center" zoom="$ctrl.map.zoom" draggable="true" options="$ctrl.map.options">
+1 -1
upvcarshare/templates/partials/journeys/origin_destiny_select.html
··· 14 14 <div class="col-sm-5"> 15 15 {% block residence_action %} 16 16 <div class="btn-group float-xs-right" role="group" aria-label="{% trans "Acciones" %}"> 17 - <a href="{% url "journeys:create-residence" %}" class="btn btn-outline-success">{% trans "Crear nuevo lugar" %}</a> 17 + <a href="{% url "journeys:create-residence" %}?next=/journeys/create/" class="btn btn-outline-success">{% trans "Crear nuevo lugar" %}</a> 18 18 </div> 19 19 {% endblock %} 20 20 </div>
+7 -2
upvcarshare/templates/residences/create.html
··· 15 15 {% block content %} 16 16 <div class="row"> 17 17 <div class="col-xs-12"> 18 - <form method="post" action=""> 18 + <form method="post" action="" residence-form> 19 19 {% csrf_token %} 20 20 {% for field in form %} 21 21 {% if field.name == "position" %} ··· 27 27 position-field-id="{{ form.position.auto_id }}" 28 28 radius-value="{{ form.distance.value }}" 29 29 radius-field="{{ form.distance.name }}" 30 - radius-field-id="{{ form.distance.auto_id }}"> 30 + radius-field-id="{{ form.distance.auto_id }}" 31 + on-update="onUpdateAddress(value)"> 31 32 </circle-map> 32 33 <span class="text-muted">{{ field.help_text }}</span> 33 34 <span class="text-muted">{{ field.errors }}</span> ··· 35 36 </div> 36 37 {% elif field.name == "distance" %} 37 38 {% else %} 39 + {% if not field.is_hidden %} 38 40 <div class="form-group row{% if field.errors %} has-danger{% endif %}"> 39 41 <label for="{{ field.auto_id }}" class="col-sm-3 form-control-label">{{ field.label }}</label> 40 42 <div class="col-sm-9"> ··· 43 45 <span class="text-muted">{{ field.errors }}</span> 44 46 </div> 45 47 </div> 48 + {% else %} 49 + {{ field }} 50 + {% endif %} 46 51 {% endif %} 47 52 {% endfor %} 48 53 <div class="form-group row">