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.

Merge branch 'develop'

+1228 -378
+2 -1
package.json
··· 12 12 "build": "gulp deploy" 13 13 }, 14 14 "devDependencies": { 15 - "angular": "^1.5.5", 15 + "angular": "^1.5.11", 16 16 "angular-cookies": "^1.5.7", 17 17 "angular-google-maps": "^2.3.3", 18 18 "angular-i18n": "^1.5.9", ··· 41 41 "lodash": "^4.13.1", 42 42 "moment": "^2.13.0", 43 43 "node-sass": "^3.4.2", 44 + "rrule": "^2.2.0", 44 45 "tether": "^1.3.2", 45 46 "vinyl-buffer": "^1.0.0", 46 47 "vinyl-source-stream": "^1.1.0",
+4 -4
upvcarshare/config/settings/test.py
··· 12 12 # ------------------------------------------------------------------------------ 13 13 # Database in memory for tests 14 14 DATABASES = { 15 - "default": { 16 - "ENGINE": "django.contrib.gis.db.backends.spatialite", 17 - "NAME": ":memory:", 18 - }, 15 + 'default': env.db( 16 + 'DATABASE_URL', default='spatialite:///{}'.format(str(ROOT_DIR.path('{}.db'.format(PROJECT_NAME.lower())))) 17 + ), 19 18 } 19 + DATABASES['default']['PORT'] = str(DATABASES['default']['PORT']) # Fix a problem with Oracle connector 20 20 DATABASES['default']['ATOMIC_REQUESTS'] = True 21 21 SPATIALITE_LIBRARY_PATH = env('SPATIALITE_LIBRARY_PATH', default='/usr/local/lib/mod_spatialite.dylib')
+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
+7 -3
upvcarshare/journeys/admin.py
··· 9 9 from journeys import DEFAULT_GOOGLE_MAPS_SRID, DEFAULT_PROJECTED_SRID, DEFAULT_WGS84_SRID 10 10 from journeys.helpers import make_point_projected, make_point 11 11 from journeys.models import Residence, Journey, Campus, Message, Transport, \ 12 - Passenger 12 + Passenger, JourneyTemplate 13 13 14 14 15 15 class PlaceAdminForm(forms.ModelForm): ··· 63 63 64 64 @admin.register(Journey) 65 65 class JourneyAdmin(admin.ModelAdmin): 66 - list_display = ["id", "residence", "campus", "kind", "departure", 67 - "created"] 66 + list_display = ["id", "departure", "created"] 67 + 68 + 69 + @admin.register(JourneyTemplate) 70 + class JourneyAdmin(admin.ModelAdmin): 71 + list_display = ["id", "residence", "campus", "kind", "created"] 68 72 69 73 70 74 @admin.register(Passenger)
+4 -4
upvcarshare/journeys/api/v1/resources.py
··· 77 77 def get_queryset(self): 78 78 queryset = super(JourneyResource, self).get_queryset() 79 79 if self.request.query_params.get('owned'): 80 - queryset = queryset.filter(user=self.request.user) 80 + queryset = queryset.filter(template__user=self.request.user) 81 81 if self.request.query_params.get('joined'): 82 82 queryset = queryset.filter(passengers__user=self.request.user) 83 83 return queryset ··· 200 200 } 201 201 pk = kwargs.get('pk', None) 202 202 if pk is not None: 203 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 203 + journey = get_object_or_404(Journey, pk=pk, template__user=request.user) 204 204 args["journey"] = journey 205 205 queryset = Journey.objects.recommended(**args) 206 206 page = self.paginate_queryset(queryset) ··· 231 231 def recurrence(self, request, **kwargs): 232 232 pk = kwargs.get('pk', None) 233 233 journey = get_object_or_404(Journey, pk=pk) 234 - queryset = journey.recurrence_journeys() 234 + queryset = journey.brothers() 235 235 page = self.paginate_queryset(queryset) 236 236 if page is not None: 237 237 serializer = self.get_serializer(page, many=True) ··· 256 256 @staticmethod 257 257 def cancel(request, **kwargs): 258 258 pk = kwargs.get('pk', 0) 259 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 259 + journey = get_object_or_404(Journey, pk=pk, template__user=request.user) 260 260 journey.cancel() 261 261 return Response(status=status.HTTP_201_CREATED) 262 262
+44 -54
upvcarshare/journeys/forms.py
··· 5 5 6 6 import floppyforms 7 7 import pytz 8 + import re 8 9 from django import forms 9 10 from django.contrib.gis.geos import GEOSGeometry 10 11 from django.core.exceptions import ObjectDoesNotExist 12 + from django.utils import timezone 11 13 from django.utils.timezone import make_aware 12 14 from django.utils.translation import ugettext_lazy as _ 13 - from django.utils import timezone 14 15 15 16 from journeys import JOURNEY_KINDS, GOING, RETURN, DEFAULT_GOOGLE_MAPS_SRID, \ 16 17 DEFAULT_PROJECTED_SRID, DEFAULT_WGS84_SRID 17 18 from journeys.helpers import expand, make_point 18 - from journeys.models import Residence, Journey, Campus, Transport 19 + from journeys.models import Residence, Journey, Campus, Transport, JourneyTemplate 19 20 from users.models import User 20 21 21 22 ··· 34 35 fields = ["name", "address", "position", "distance"] 35 36 widgets = { 36 37 "name": forms.TextInput(attrs={"class": "form-control"}), 37 - "address": forms.Textarea(attrs={"class": "form-control"}), 38 + "address": forms.HiddenInput(attrs={"class": "form-control", "ng-value": "address"}), 38 39 } 39 40 40 41 def clean_position(self): ··· 62 63 63 64 class JourneyForm(forms.ModelForm): 64 65 65 - i_am_driver = forms.BooleanField( 66 - label=_("¿Eres conductor?"), 67 - required=False, 68 - initial=False, 69 - widget=forms.RadioSelect( 70 - choices=((True, _('Sí')), (False, _('No'))), 71 - ) 72 - ) 73 - 74 66 class Meta: 75 67 model = Journey 76 - fields = ["residence", "campus", "kind", "i_am_driver", "transport", "free_places", "departure", "time_window", 77 - "arrival", "recurrence"] 68 + fields = ["free_places", "departure", "arrival"] 78 69 widgets = { 79 - "transport": forms.Select(attrs={"class": "form-control"}), 80 - "residence": forms.Select(attrs={"class": "form-control"}), 81 - "campus": forms.Select(attrs={"class": "form-control"}), 82 - "kind": forms.Select(attrs={"class": "form-control"}), 83 70 "free_places": forms.NumberInput(attrs={"class": "form-control"}), 84 71 "departure": floppyforms.DateTimeInput(attrs={"class": "form-control"}), 85 72 "arrival": floppyforms.DateTimeInput(attrs={"class": "form-control"}), 86 - "time_window": forms.NumberInput(attrs={"class": "form-control"}), 87 73 } 88 74 89 75 def __init__(self, *args, **kwargs): 90 76 self.user = kwargs.pop("user") 91 77 super(JourneyForm, self).__init__(*args, **kwargs) 92 - if self.user: 93 - self.fields['residence'].queryset = Residence.objects.filter(user=self.user) 94 - self.fields['transport'].queryset = Transport.objects.filter(user=self.user) 95 78 96 79 def clean_free_places(self): 97 80 free_places = self.cleaned_data["free_places"] 98 - transport = self.cleaned_data["transport"] 81 + transport = self.instance.template.transport if self.instance is not None else None 99 82 if transport is not None and free_places > transport.default_places: 100 83 raise forms.ValidationError(_("No puedes ofertar más plazas que las que tienes en el transporte")) 101 84 return free_places ··· 124 107 raise forms.ValidationError(_("No puedes crear viajes que llegues antes de salir")) 125 108 return arrival 126 109 127 - def save(self, commit=True, **kwargs): 128 - """When save a journey form, you have to provide an user.""" 129 - user = self.user 130 - if "user" in kwargs: 131 - assert isinstance(kwargs["user"], User) 132 - user = kwargs.get("user") 133 - journey = super(JourneyForm, self).save(commit=False) 134 - journey.user = user 135 - journey.driver = user if self.cleaned_data["i_am_driver"] else None 136 - if commit: 137 - journey.save() 138 - return journey 139 110 111 + class SmartJourneyTemplateForm(forms.ModelForm): 140 112 141 - class SmartJourneyForm(forms.ModelForm): 142 - 143 - origin = forms.CharField(widget=forms.HiddenInput()) 144 - destiny = forms.CharField(widget=forms.HiddenInput()) 113 + origin = forms.CharField(widget=forms.HiddenInput(), required=False) 114 + destiny = forms.CharField(widget=forms.HiddenInput(), required=False) 145 115 146 116 i_am_driver = forms.BooleanField( 147 117 label=_("¿Soy conductor?*"), ··· 156 126 ) 157 127 ) 158 128 129 + free_places = forms.IntegerField( 130 + label=_("Plazas libres"), 131 + required=False, 132 + widget=forms.NumberInput(attrs={"class": "form-control"}), 133 + help_text=_("Dejar en blanco para usar el valor por defecto del transporte seleccionado") 134 + ) 135 + 159 136 class Meta: 160 - model = Journey 137 + model = JourneyTemplate 161 138 fields = ["origin", "destiny", "i_am_driver", "transport", 162 - "free_places", "departure", "arrival", "time_window", 163 - "recurrence"] 139 + "free_places", "departure", "recurrence", "arrival", "time_window"] 164 140 widgets = { 165 141 "transport": forms.Select(attrs={"class": "form-control"}), 166 142 "kind": forms.Select(attrs={"class": "form-control"}), ··· 172 148 attrs={"class": "form-control"} 173 149 ), 174 150 "time_window": forms.NumberInput(attrs={"class": "form-control"}), 151 + "recurrence": forms.HiddenInput() 175 152 } 176 153 177 154 def __init__(self, *args, **kwargs): 178 155 self.user = kwargs.pop("user") 179 - super(SmartJourneyForm, self).__init__(*args, **kwargs) 156 + super(SmartJourneyTemplateForm, self).__init__(*args, **kwargs) 180 157 if self.user: 181 158 self.fields['transport'].queryset = Transport.objects.filter( 182 159 user=self.user ··· 184 161 185 162 def clean_origin(self): 186 163 origin = self.cleaned_data["origin"] 164 + if not origin: 165 + raise forms.ValidationError(_("El origen obligatorio")) 187 166 data = origin.split(":") 188 167 models = {"residence": Residence, "campus": Campus} 189 168 try: ··· 193 172 194 173 def clean_destiny(self): 195 174 destiny = self.cleaned_data["destiny"] 175 + if not destiny: 176 + raise forms.ValidationError(_("El destino obligatorio")) 196 177 data = destiny.split(":") 197 178 models = {"residence": Residence, "campus": Campus} 198 179 try: ··· 202 183 203 184 def clean_departure(self): 204 185 departure = self.cleaned_data["departure"] 186 + departure = timezone.localtime(departure, pytz.timezone("UTC")) 205 187 time_window = self.cleaned_data.get("time_window", 30) 206 188 now = timezone.now() 207 189 if departure < now: ··· 225 207 def clean_free_places(self): 226 208 free_places = self.cleaned_data["free_places"] 227 209 transport = self.cleaned_data["transport"] 210 + if not free_places and transport: 211 + free_places = transport.default_places 228 212 if transport is not None and free_places > transport.default_places: 229 213 raise forms.ValidationError(_("No puedes ofertar más plazas que las que tienes en el transporte")) 230 214 return free_places 231 215 216 + def clean_recurrence(self): 217 + """Delete bad data from recurrence.""" 218 + recurrence = self.cleaned_data["recurrence"] 219 + recurrence = re.sub(r"DTSTART=.+;", "", recurrence) 220 + recurrence = re.sub(r"BYSECOND=NAN", "", recurrence) 221 + return recurrence 222 + 232 223 def save(self, commit=True, **kwargs): 233 224 """When save a journey form, you have to provide an user.""" 234 225 user = self.user 235 226 if "user" in kwargs: 236 227 assert isinstance(kwargs["user"], User) 237 228 user = kwargs.get("user") 238 - journey = super(SmartJourneyForm, self).save(commit=False) 239 - journey.user = user 240 - journey.driver = user if self.cleaned_data["i_am_driver"] else None 229 + journey_template = super(SmartJourneyTemplateForm, self).save(commit=False) 230 + journey_template.user = user 231 + journey_template.driver = user if self.cleaned_data["i_am_driver"] else None 241 232 # Smart origin, destiny and kind 242 233 origin = self.cleaned_data["origin"] 243 234 destiny = self.cleaned_data["destiny"] ··· 246 237 Campus: "campus", 247 238 } 248 239 attribute = attribute_selector[origin.__class__] 249 - setattr(journey, attribute, origin) 240 + setattr(journey_template, attribute, origin) 250 241 attribute = attribute_selector[destiny.__class__] 251 - setattr(journey, attribute, destiny) 252 - journey.kind = GOING if isinstance(origin, Residence) else RETURN 242 + setattr(journey_template, attribute, destiny) 243 + journey_template.kind = GOING if isinstance(origin, Residence) else RETURN 253 244 if commit: 254 - journey.save() 245 + journey_template.save() 255 246 # Expand journey recurrence 256 - journeys = expand(journey) 257 - Journey.objects.bulk_create(journeys) 258 - return journey 247 + expand(journey_template) 248 + return journey_template 259 249 260 250 261 251 class FilterForm(forms.Form): ··· 384 374 distance = self.cleaned_data["distance"] 385 375 departure_date = self.cleaned_data["departure_date"] 386 376 departure_time = self.cleaned_data["departure_time"] 387 - departure = datetime.datetime.combine(departure_date, departure_time) 377 + departure = make_aware(datetime.datetime.combine(departure_date, departure_time), timezone=pytz.timezone("UTC")) 388 378 search_by_time = self.cleaned_data.get("search_by_time", False) 389 379 time_window = self.cleaned_data["time_window"] 390 380 return Journey.objects.search(
+14 -27
upvcarshare/journeys/helpers.py
··· 6 6 7 7 from django.contrib.gis.gdal import SpatialReference, CoordTransform 8 8 from django.utils import timezone 9 + from django.utils.timezone import make_aware 9 10 10 11 from journeys import DEFAULT_PROJECTED_SRID, DEFAULT_WGS84_SRID 11 12 ··· 48 49 return first_day_current_week() + datetime.timedelta(days=7) 49 50 50 51 51 - def expand(journey): 52 - """Expands given journey using recurrence field to create new journeys.""" 53 - from journeys.models import Journey 54 - 55 - # Finish date is 1 of september, new course 56 - today = journey.departure 52 + def default_until(today): 57 53 finish_date = today.replace(day=1, month=9) 58 54 if today.month >= 9: 59 55 finish_date = finish_date.replace(year=finish_date.year + 1) 56 + return finish_date 57 + 58 + 59 + def expand(journey_template): 60 + """Expands given journey using recurrence field to create new journeys.""" 61 + recurrence_dates = journey_template.recurrence_dates() 60 62 journeys = [] 61 - if journey.recurrence: 62 - datetime_start = journey.departure + datetime.timedelta(days=1) 63 - datetime_end = datetime.datetime.combine(finish_date, time=datetime.time(0, 0, 0, 0)) 64 - if journey.recurrence.dtend: 65 - datetime_end = min(journey.recurrence.dtend, datetime_end) 66 - for date in journey.recurrence.occurrences(dtstart=datetime_start, dtend=datetime_end): 67 - new_journey = Journey.objects.get(pk=journey.pk) 68 - new_journey.pk = None 69 - new_journey.update_modified = True # TODO I dot know why this is needed, sorry :( 70 - new_journey.parent = journey 71 - new_journey.departure = date.replace( 72 - hour=new_journey.departure.hour, 73 - minute=new_journey.departure.minute, 74 - tzinfo=new_journey.departure.tzinfo 63 + for dates in recurrence_dates: 64 + journeys.append( 65 + journey_template.create_journey( 66 + departure=dates[0], 67 + arrival=dates[1] 75 68 ) 76 - if new_journey.arrival: 77 - new_journey.arrival = date.replace( 78 - hour=new_journey.arrival.hour, 79 - minute=new_journey.arrival.minute, 80 - tzinfo=new_journey.arrival.tzinfo 81 - ) 82 - journeys.append(new_journey) 69 + ) 83 70 return journeys
+43 -36
upvcarshare/journeys/managers.py
··· 21 21 :param journey: 22 22 :param override_distance: 23 23 """ 24 - key = "residence{}" if journey.kind == GOING else "campus{}" 25 - distance = getattr(journey, key.format("")).distance if override_distance is None else override_distance 24 + template = journey.template 25 + template_key = "residence{}" if journey.template.kind == GOING else "campus{}" 26 + key = "template__%s" % template_key 27 + distance = getattr(template, template_key.format("")).distance \ 28 + if override_distance is None else override_distance 26 29 return { 27 30 key.format("__position__distance_lte"): ( 28 - getattr(journey, key.format("")).position, 31 + getattr(template, template_key.format("")).position, 29 32 D(m=distance) 30 33 ), 31 - "departure__lte": journey.departure + datetime.timedelta(minutes=journey.time_window), 32 - "departure__gte": journey.departure - datetime.timedelta(minutes=journey.time_window), 34 + "departure__lte": journey.departure + datetime.timedelta(minutes=journey.template.time_window), 35 + "departure__gte": journey.departure - datetime.timedelta(minutes=journey.template.time_window), 33 36 } 34 37 35 38 ··· 47 50 ) 48 51 49 52 50 - class JourneyQuerySet(models.QuerySet): 51 - 52 - def visible(self, user=None): 53 - """Journey visible for the given user.""" 54 - if user is not None: 55 - return self.filter(user__groups=user.groups.all()) 56 - return self 57 - 58 - 59 - class JourneyManager(models.GeoManager): 60 - """Manager for Journeys.""" 61 - 62 - def get_queryset(self): 63 - return JourneyQuerySet(self.model, using=self._db) 64 - 65 - def visible(self, user=None): 66 - """Journey visible for the given user.""" 67 - return self.get_queryset().visible(user=user) 53 + class JourneyTemplateManager(models.GeoManager): 68 54 69 55 def smart_create(self, user, origin, destination, departure, transport=None): 70 56 """Enhanced method to create journeys""" ··· 83 69 } 84 70 if transport is not None: 85 71 data["driver"] = user 86 - data["free_places"] = transport.default_places 72 + # data["free_places"] = transport.default_places 87 73 return self.create(**data) 88 74 75 + 76 + class JourneyQuerySet(models.QuerySet): 77 + 78 + def visible(self, user=None): 79 + """Journey visible for the given user.""" 80 + if user is not None: 81 + return self.filter(template__user__groups=user.groups.all()) 82 + return self 83 + 84 + 85 + class JourneyManager(models.GeoManager): 86 + """Manager for Journeys.""" 87 + 88 + def get_queryset(self): 89 + return JourneyQuerySet(self.model, using=self._db) 90 + 91 + def visible(self, user=None): 92 + """Journey visible for the given user.""" 93 + return self.get_queryset().visible(user=user) 94 + 89 95 def available(self, user=None, kind=None, ignore_full=False): 90 96 """Gets all available journeys. 91 97 :param user: ··· 93 99 :param ignore_full: 94 100 """ 95 101 now = timezone.now() 96 - queryset = self.visible(user).filter(driver__isnull=False, departure__gt=now) 102 + queryset = self.visible(user).filter(template__driver__isnull=False, departure__gt=now) 97 103 if kind is not None: 98 - queryset = queryset.filter(kind=kind) 104 + queryset = queryset.filter(template__kind=kind) 99 105 if ignore_full: 100 106 return queryset 101 107 # NOTE: annotate QuerySet method has problems with Oracle, so, we have to ··· 112 118 113 119 nearby = self.available(kind=kind) 114 120 if kind is not None: 115 - key = "residence{}" if kind == GOING else "campus{}" 121 + key = "template__residence{}" if kind == GOING else "template__campus{}" 116 122 key = key.format("__position__distance_lte") 117 123 nearby = nearby.filter(**{key: (geometry, distance)}) 118 124 else: 119 125 nearby = nearby.filter( 120 - Q(residence__position__distance_lte=(geometry, distance)) | 121 - Q(campus__position__distance_lte=(geometry, distance)) 126 + Q(template__residence__position__distance_lte=(geometry, distance)) | 127 + Q(template__campus__position__distance_lte=(geometry, distance)) 122 128 ) 123 129 return nearby 124 130 ··· 128 134 :param user: 129 135 """ 130 136 now = timezone.now() 131 - queryset = self.filter(user=user, driver__isnull=True, departure__gt=now) 137 + queryset = self.filter(template__user=user, template__driver__isnull=True, departure__gt=now) 132 138 if kind is not None: 133 - queryset = queryset.filter(kind=kind) 139 + queryset = queryset.filter(template__kind=kind) 134 140 return queryset 135 141 136 142 def recommended(self, user, kind=None, journey=None, override_distance=None, ignore_full=False): ··· 152 158 if not conditions: 153 159 return self.none() 154 160 now = timezone.now() 155 - queryset = self.available(user=user, kind=kind, ignore_full=ignore_full).exclude(user=user, departure__lt=now)\ 161 + queryset = self.available(user=user, kind=kind, ignore_full=ignore_full)\ 162 + .exclude(template__user=user, departure__lt=now)\ 156 163 .filter(reduce(lambda x, y: x | y, conditions))\ 157 164 .order_by("departure") 158 165 return queryset ··· 177 184 kinds = [GOING, RETURN] 178 185 conditions = [] 179 186 for kind in kinds: 180 - key = "residence{}" if kind == GOING else "campus{}" 187 + key = "template__residence{}" if kind == GOING else "template__campus{}" 181 188 conditions.append(Q(**{ 182 189 key.format("__position__distance_lte"): ( 183 190 position, ··· 188 195 })) 189 196 now = timezone.now() 190 197 queryset = self.available(user=user, ignore_full=ignore_full)\ 191 - .exclude(user=user, departure__lt=now, disabled=True) \ 198 + .exclude(template__user=user, departure__lt=now, disabled=True) \ 192 199 .filter(reduce(lambda x, y: x | y, conditions)) \ 193 200 .order_by("departure") 194 201 return queryset 195 202 196 203 def overlaps(self, user, departure, time_window): 197 204 """Returns a queryset with the overlapping journeys.""" 198 - return self.filter(user=user).filter( 205 + return self.filter(template__user=user).filter( 199 206 departure__gte=(departure - datetime.timedelta(minutes=time_window)), 200 207 departure__lte=(departure + datetime.timedelta(minutes=time_window)) 201 208 ) ··· 214 221 :param journey: 215 222 """ 216 223 if journey is None: 217 - return self.filter(Q(journey__user=user) | Q(journey__passengers__user=user)) 224 + return self.filter(Q(journey__template__user=user) | Q(journey__passengers__user=user)) 218 225 if not journey.is_messenger_allowed(user): 219 226 return self.none() 220 227 return self.filter(journey=journey)
+82
upvcarshare/journeys/migrations/0015_auto_20170624_1433.py
··· 1 + # -*- coding: utf-8 -*- 2 + # Generated by Django 1.10.2 on 2017-06-24 14:33 3 + from __future__ import unicode_literals 4 + 5 + from django.conf import settings 6 + from django.db import migrations, models 7 + import django.db.models.deletion 8 + import django_extensions.db.fields 9 + import recurrence.fields 10 + 11 + 12 + class Migration(migrations.Migration): 13 + 14 + dependencies = [ 15 + migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 + ('journeys', '0014_auto_20170530_0923'), 17 + ] 18 + 19 + operations = [ 20 + migrations.CreateModel( 21 + name='JourneyTemplate', 22 + fields=[ 23 + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 + ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')), 25 + ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')), 26 + ('time_window', models.PositiveIntegerField(blank=True, default=30, help_text='Se buscarán por los viajes que salgan hasta con estos minutos de antelación', verbose_name='ventana de tiempo')), 27 + ('recurrence', recurrence.fields.RecurrenceField(blank=True, null=True, verbose_name='¿Vas a realizar este viaje más de una vez?')), 28 + ('kind', models.PositiveIntegerField(choices=[(0, 'ida'), (1, 'vuelta')], verbose_name='tipo de viaje')), 29 + ('campus', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='journeys', to='journeys.Campus', verbose_name='campus')), 30 + ('driver', models.ForeignKey(blank=True, help_text='user who drives during the journey', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 31 + ('residence', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='journeys', to='journeys.Residence', verbose_name='lugar')), 32 + ('transport', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='journeys', to='journeys.Transport', verbose_name='Medio de transporte utilizado')), 33 + ('user', models.ForeignKey(help_text='user who creates the journey', on_delete=django.db.models.deletion.CASCADE, related_name='journeys', to=settings.AUTH_USER_MODEL)), 34 + ], 35 + options={ 36 + 'ordering': ('-modified', '-created'), 37 + 'abstract': False, 38 + 'get_latest_by': 'modified', 39 + }, 40 + ), 41 + migrations.RemoveField( 42 + model_name='journey', 43 + name='campus', 44 + ), 45 + migrations.RemoveField( 46 + model_name='journey', 47 + name='driver', 48 + ), 49 + migrations.RemoveField( 50 + model_name='journey', 51 + name='kind', 52 + ), 53 + migrations.RemoveField( 54 + model_name='journey', 55 + name='parent', 56 + ), 57 + migrations.RemoveField( 58 + model_name='journey', 59 + name='recurrence', 60 + ), 61 + migrations.RemoveField( 62 + model_name='journey', 63 + name='residence', 64 + ), 65 + migrations.RemoveField( 66 + model_name='journey', 67 + name='time_window', 68 + ), 69 + migrations.RemoveField( 70 + model_name='journey', 71 + name='transport', 72 + ), 73 + migrations.RemoveField( 74 + model_name='journey', 75 + name='user', 76 + ), 77 + migrations.AddField( 78 + model_name='journey', 79 + name='template', 80 + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='journeys', to='journeys.JourneyTemplate'), 81 + ), 82 + ]
+20
upvcarshare/journeys/migrations/0016_auto_20170624_1958.py
··· 1 + # -*- coding: utf-8 -*- 2 + # Generated by Django 1.10.2 on 2017-06-24 19:58 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', '0015_auto_20170624_1433'), 12 + ] 13 + 14 + operations = [ 15 + migrations.AlterField( 16 + model_name='journeytemplate', 17 + name='recurrence', 18 + field=models.TextField(blank=True, null=True, verbose_name='¿Vas a realizar este viaje más de una vez?'), 19 + ), 20 + ]
+25
upvcarshare/journeys/migrations/0017_auto_20170624_2019.py
··· 1 + # -*- coding: utf-8 -*- 2 + # Generated by Django 1.10.2 on 2017-06-24 20:19 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', '0016_auto_20170624_1958'), 12 + ] 13 + 14 + operations = [ 15 + migrations.AddField( 16 + model_name='journeytemplate', 17 + name='arrival', 18 + field=models.DateTimeField(blank=True, null=True, verbose_name='Cuándo creo que voy a llegar*'), 19 + ), 20 + migrations.AddField( 21 + model_name='journeytemplate', 22 + name='departure', 23 + field=models.DateTimeField(null=True, verbose_name='Cuándo voy a realizar el viaje*'), 24 + ), 25 + ]
+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 + ]
+190 -76
upvcarshare/journeys/models.py
··· 2 2 from __future__ import unicode_literals, print_function, absolute_import 3 3 4 4 from copy import copy 5 + from dateutil.rrule import rrulestr 6 + from django.utils.timezone import make_aware 5 7 from functools import reduce 6 8 import datetime 7 9 ··· 21 23 from journeys import JOURNEY_KINDS, GOING, RETURN, DEFAULT_DISTANCE, DEFAULT_PROJECTED_SRID, DEFAULT_WGS84_SRID, \ 22 24 DEFAULT_TIME_WINDOW, PASSENGER_STATUSES, UNKNOWN, CONFIRMED, REJECTED, DEFAULT_GOOGLE_MAPS_SRID 23 25 from journeys.exceptions import NoFreePlaces, NotAPassenger, AlreadyAPassenger 24 - from journeys.helpers import make_point_wgs84, make_point 25 - from journeys.managers import JourneyManager, ResidenceManager, MessageManager 26 + from journeys.helpers import make_point_wgs84, make_point, default_until 27 + from journeys.managers import JourneyManager, ResidenceManager, MessageManager, JourneyTemplateManager 26 28 from notifications import JOIN, LEAVE, CANCEL, CONFIRM, REJECT, THROW_OUT 27 29 from notifications.decorators import dispatch 28 30 ··· 80 82 here. Each residence belongs to a user. 81 83 """ 82 84 user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="residences") 83 - 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 + ) 84 91 85 92 objects = ResidenceManager() 86 93 ··· 111 118 112 119 113 120 @python_2_unicode_compatible 114 - class Journey(GisTimeStampedModel): 115 - """A model class to represent a journey between two node.""" 121 + class JourneyTemplate(GisTimeStampedModel): 122 + """A journey template is used to create a serie of journeys using the reference of itself.""" 123 + 124 + # Owner of the template 116 125 user = models.ForeignKey( 117 126 settings.AUTH_USER_MODEL, related_name="journeys", help_text=_("user who creates the journey") 118 127 ) 128 + 129 + # Data about the driver 119 130 driver = models.ForeignKey( 120 131 settings.AUTH_USER_MODEL, null=True, blank=True, help_text=_("user who drives during the journey") 121 132 ) 133 + 134 + # Data about origin and destination, and time window to make its 135 + # journeys visibles. 122 136 residence = models.ForeignKey( 123 137 "journeys.Residence", 124 138 verbose_name=_("lugar"), ··· 129 143 verbose_name=_("campus"), 130 144 related_name="journeys" 131 145 ) 132 - kind = models.PositiveIntegerField(choices=JOURNEY_KINDS, verbose_name=_("tipo de viaje")) 133 - free_places = models.PositiveIntegerField(default=4, verbose_name=_("plazas libres"), blank=True, null=True) 134 - total_passengers = models.PositiveIntegerField(default=0, verbose_name=_("total pasajeros"), blank=True, null=True) 135 - departure = models.DateTimeField(verbose_name=_("fecha y hora de salida*")) 136 - arrival = models.DateTimeField(verbose_name=_("fecha y hora de llegada estimada*"), null=True, blank=True) 137 146 time_window = models.PositiveIntegerField( 138 147 verbose_name=_("ventana de tiempo"), 139 148 help_text=_("Se buscarán por los viajes que salgan hasta con estos minutos de antelación"), 140 149 default=DEFAULT_TIME_WINDOW, 141 150 blank=True 142 151 ) 152 + 153 + # Data about recurrence 154 + departure = models.DateTimeField(verbose_name=_("Cuándo voy a realizar el viaje*"), null=True) 155 + arrival = models.DateTimeField(verbose_name=_("Cuándo creo que voy a llegar*"), null=True, blank=True) 156 + recurrence = models.TextField( 157 + verbose_name=_("¿Vas a realizar este viaje más de una vez?"), 158 + null=True, 159 + blank=True 160 + ) 161 + 162 + # Kind of the journey 163 + kind = models.PositiveIntegerField(choices=JOURNEY_KINDS, verbose_name=_("tipo de viaje")) 164 + 165 + # Transport used in this journeys 143 166 transport = models.ForeignKey( 144 167 "journeys.Transport", 145 168 related_name="journeys", ··· 147 170 null=True, 148 171 blank=True 149 172 ) 173 + 174 + objects = JourneyTemplateManager() 175 + 176 + def has_recurrence(self): 177 + return self.recurrence is not None and self.recurrence != "" 178 + 179 + def create_journey(self, departure, arrival): 180 + """Creates a Journey using the template and the data given.""" 181 + attributes = { 182 + "template": self, 183 + "departure": departure, 184 + "arrival": arrival, 185 + } 186 + if self.driver is not None: 187 + attributes["free_places"] = self.transport.default_places if self.transport is not None else 4 188 + attributes["total_passengers"] = 0 189 + journey = Journey(**attributes) 190 + journey.save() 191 + return journey 192 + 193 + def recurrence_dates(self): 194 + """Returns a list of (departure, arrival) datetimes, to create the list of 195 + journeys. 196 + """ 197 + if self.has_recurrence(): 198 + interval = self.arrival - self.departure 199 + rules = rrulestr(self.recurrence, dtstart=self.departure) 200 + if rules._until is None: 201 + rules._until = default_until(self.departure) 202 + dates = list(rules) 203 + if dates and dates[0].tzinfo is None: 204 + dates = list(map(lambda d: make_aware(d), dates)) 205 + return zip(dates, map(lambda d: d + interval, dates)) 206 + return [(self.departure, self.arrival)] 207 + 208 + def save(self, **kwargs): 209 + """Override save to set the default time window value. Default value not set in Oracle.""" 210 + if not self.time_window: 211 + self.time_window = DEFAULT_TIME_WINDOW 212 + return super(JourneyTemplate, self).save(**kwargs) 213 + 214 + 215 + @python_2_unicode_compatible 216 + class Journey(GisTimeStampedModel): 217 + """A model class to represent a journey between two node.""" 218 + 219 + # Reference to the template 220 + template = models.ForeignKey("journeys.JourneyTemplate", related_name="journeys", null=True) 221 + 222 + # Data about places and passengers 223 + free_places = models.PositiveIntegerField(default=4, verbose_name=_("plazas libres"), blank=True, null=True) 224 + total_passengers = models.PositiveIntegerField(default=0, verbose_name=_("total pasajeros"), blank=True, null=True) 225 + 226 + # Data about time of departure and arrival 227 + departure = models.DateTimeField(verbose_name=_("fecha y hora de salida*")) 228 + arrival = models.DateTimeField(verbose_name=_("fecha y hora de llegada estimada*"), null=True, blank=True) 229 + 150 230 disabled = models.BooleanField(default=False, verbose_name=_("marcar como deshabilitado")) 151 - recurrence = RecurrenceField(verbose_name=_("¿Vas a realizar este viaje más de una vez?"), null=True, blank=True) 152 - parent = models.ForeignKey("journeys.Journey", null=True, blank=True, related_name="children") 153 231 154 232 objects = JourneyManager() 155 233 156 234 @property 157 235 def origin(self): 158 236 """Origin of the journey.""" 159 - if self.kind == GOING: 160 - return self.residence 161 - return self.campus 237 + if self.template.kind == GOING: 238 + return self.template.residence 239 + return self.template.campus 162 240 163 241 @property 164 242 def destination(self): 165 243 """Destination of the journey.""" 166 - if self.kind == RETURN: 167 - return self.residence 168 - return self.campus 244 + if self.template.kind == RETURN: 245 + return self.template.residence 246 + return self.template.campus 169 247 170 248 @property 171 249 def has_recurrence(self): 172 - return self.children.exists() or self.parent is not None 250 + return self.template.journeys.count() > 1 251 + 252 + @property 253 + def user(self): 254 + return self.template.user 255 + 256 + @property 257 + def residence(self): 258 + return self.template.residence 259 + 260 + @property 261 + def campus(self): 262 + return self.template.campus 263 + 264 + @property 265 + def driver(self): 266 + return self.template.driver 267 + 268 + @property 269 + def kind(self): 270 + return self.template.kind 173 271 174 272 def __str__(self): 175 273 return self.description(strip_html=True) ··· 188 286 return self.arrival.isoformat() 189 287 return (self.departure + datetime.timedelta(minutes=30)).isoformat() 190 288 191 - def recurrence_journeys(self): 192 - journeys = Journey.objects.filter(pk=self.pk) 193 - if self.children.exists(): 194 - journeys = Journey.objects.filter( 195 - Q(pk__in=self.children.all().values_list("pk", flat=True)) | 196 - Q(pk=self.pk) 197 - ) 198 - elif self.parent: 199 - journeys = self.parent.children.all() 200 - journeys = Journey.objects.filter( 201 - Q(pk__in=self.parent.children.all().values_list("pk", flat=True)) | 202 - Q(pk=self.parent.pk) 203 - ) 204 - return journeys 289 + def brothers(self, exclude_myself=False): 290 + """Gets the 'brothers' of this journey.""" 291 + if not exclude_myself: 292 + return self.template.journeys.all() 293 + return self.template.journeys.exclude(pk=self.pk) 205 294 206 295 def description(self, strip_html=False): 207 296 """Gets a human read description of the journey.""" 208 - if self.kind == GOING: 297 + if self.template.kind == GOING: 209 298 value = _("Viaje de <strong>%(kind)s</strong> a <strong>%(campus_name)s</strong>") % { 210 - "kind": self.get_kind_display(), 211 - "campus_name": self.campus.name 299 + "kind": self.template.get_kind_display(), 300 + "campus_name": self.template.campus.name 212 301 } 213 302 else: 214 303 value = _("Viaje de <strong>%(kind)s</strong> desde <strong>%(campus_name)s</strong>") % { 215 - "kind": self.get_kind_display(), 216 - "campus_name": self.campus.name 304 + "kind": self.template.get_kind_display(), 305 + "campus_name": self.template.campus.name 217 306 } 218 307 if strip_html: 219 308 value = strip_tags(value) ··· 229 318 return self.free_places - self.count_passengers() 230 319 return 0 231 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 + 232 332 @dispatch(JOIN) 233 333 def join_passenger(self, user, join_to=None): 234 334 """A user joins a journey. ··· 237 337 """ 238 338 # Join only one 239 339 if join_to is None or join_to == "one": 240 - if self.passengers.filter(user=user).exists() or self.driver == user: 241 - raise AlreadyAPassenger() 242 - if self.count_passengers() < self.free_places: 243 - passenger = Passenger.objects.create( 244 - journey=self, 245 - user=user, 246 - status=UNKNOWN 247 - ) 248 - return passenger 340 + return self._join_passenger(user=user) 249 341 # Join to recurrence 250 342 elif join_to is not None and join_to == "all": 251 343 if self.has_recurrence: 252 - journeys = self.recurrence_journeys() 344 + journeys = self.brothers(exclude_myself=True) 253 345 journeys = journeys.filter(departure__gte=self.departure) 254 - passengers = [] 346 + passengers = [self._join_passenger(user=user)] 255 347 for journey in journeys: 256 348 try: 257 349 passengers.append(journey.join_passenger(user)) ··· 262 354 elif join_to is not None and len(join_to.split("/")) > 0: 263 355 dates = map(lambda item: datetime.datetime.strptime(item, "%d/%m/%Y"), join_to.split(",")) 264 356 conditions = [Q(departure__day=date.day, departure__month=date.month, departure__year=date.year) for date in dates] 265 - journeys = self.recurrence_journeys() 357 + journeys = self.brothers(exclude_myself=True) 266 358 journeys = journeys.filter(reduce(lambda x, y: x | y, conditions)) 267 - print(journeys) 268 - passengers = [] 359 + passengers = [self._join_passenger(user=user)] 269 360 for journey in journeys: 270 361 try: 271 362 passengers.append(journey.join_passenger(user)) ··· 274 365 return passengers 275 366 raise NoFreePlaces() 276 367 277 - @dispatch(LEAVE) 278 - def leave_passenger(self, user): 279 - """A user leave a journey. 280 - :param user: 281 - """ 368 + def _leave_passenger(self, user): 282 369 if not self.is_passenger(user=user): 283 370 raise NotAPassenger() 284 371 self.passengers.filter(user=user).delete() ··· 287 374 self.total_passengers = 0 288 375 self.save() 289 376 377 + @dispatch(LEAVE) 378 + def leave_passenger(self, user, leave_from=None): 379 + """A user leave a journey. 380 + :param leave_from: 381 + :param user: 382 + """ 383 + # Join only one 384 + if leave_from is None or leave_from == "one": 385 + return self._leave_passenger(user=user) 386 + # Join to recurrence 387 + elif leave_from is not None and leave_from == "all": 388 + if self.has_recurrence: 389 + journeys = self.brothers(exclude_myself=True) 390 + journeys = journeys.filter(departure__gte=self.departure) 391 + passengers = [self._leave_passenger(user=user)] 392 + for journey in journeys: 393 + try: 394 + passengers.append(journey.leave_passenger(user)) 395 + except NotAPassenger: 396 + pass 397 + return passengers 398 + 290 399 @dispatch(THROW_OUT) 291 400 def throw_out(self, user): 292 401 """A user is throw out from a journey. 293 402 :param user: 294 403 """ 295 - self.leave_passenger(user) 404 + self.leave_passenger(user, leave_from="all") 296 405 297 406 @dispatch(CONFIRM) 298 407 def confirm_passenger(self, user): ··· 302 411 # self.passengers.filter(user=user).update(status=CONFIRMED) 303 412 passenger = self.passengers.filter(user=user).first() 304 413 if passenger: 305 - passengers = passenger.recurrence() 414 + passengers = passenger.brothers() 306 415 passengers.update(status=CONFIRMED) 307 416 self.total_passengers += 1 308 417 self.save() ··· 315 424 # self.passengers.filter(user=user).update(status=REJECTED) 316 425 passenger = self.passengers.filter(user=user).first() 317 426 if passenger: 318 - passengers = passenger.recurrence() 427 + passengers = passenger.brothers() 319 428 passengers.update(status=REJECTED) 320 429 321 430 def is_passenger(self, user, all_passengers=False): ··· 328 437 """Gets recommended journeys for this journey. 329 438 :returns QuerySet: 330 439 """ 331 - if self.driver == self.user: 440 + if self.template.driver == self.template.user: 332 441 return Journey.objects.none() 333 - result = Journey.objects.recommended(user=self.user, kind=self.kind, journey=self, ignore_full=ignore_full) 442 + result = Journey.objects.recommended(user=self.template.user, kind=self.template.kind, journey=self, ignore_full=ignore_full) 334 443 return result 335 444 336 445 def needs_driver(self): 337 446 """Checks if the journey needs a driver.""" 338 - return self.driver is None 447 + return self.template.driver is None 339 448 340 449 def are_there_free_places(self): 341 450 """Check if there are free places.""" ··· 343 452 344 453 def is_fulfilled(self): 345 454 """Check if the journey is already fulfilled by the given user.""" 346 - return self.needs_driver() and self.recommended(ignore_full=True).filter(passengers__user=self.user).exists() 455 + return self.needs_driver() and self.recommended(ignore_full=True).\ 456 + filter(passengers__user=self.template.user).exists() 347 457 348 458 def fulfilled_by(self): 349 459 """Gets journeys who are fulfilling this one.""" 350 460 if not self.is_fulfilled(): 351 461 return None 352 - return self.recommended(ignore_full=True).filter(passengers__user=self.user) 462 + return self.recommended(ignore_full=True).filter(passengers__user=self.template.user) 353 463 354 464 def distance(self): 355 465 """Gets the journey distance.""" 356 - return self.residence.position.distance(self.campus.position) / 1000 466 + return self.template.residence.position.distance(self.campus.position) / 1000 357 467 358 468 def is_messenger_allowed(self, user): 359 469 """Check if the user is allowed to make messenger actions.""" 360 - return not(self.user != user and not self.is_passenger(user)) 470 + return not(self.template.user != user and not self.is_passenger(user)) 361 471 362 472 @dispatch(CANCEL) 363 473 def cancel(self): ··· 374 484 return Passenger.objects.none() 375 485 376 486 def save(self, **kwargs): 377 - """Override save to set the default time window value. Default value not set in Oracle.""" 378 - if not self.time_window: 379 - self.time_window = DEFAULT_TIME_WINDOW 380 - return super(Journey, self).save(**kwargs) 487 + if self.template is not None and self.departure is None: 488 + self.departure = self.template.departure 489 + if self.template is not None and self.arrival is None: 490 + self.arrival = self.template.arrival 491 + super(Journey, self).save(**kwargs) 381 492 382 493 383 494 class Passenger(TimeStampedModel): ··· 389 500 class Meta: 390 501 unique_together = ["user", "journey"] 391 502 392 - def recurrence(self): 393 - journeys = self.journey.recurrence_journeys() 394 - conditions = [Q(journey__pk=journey.pk) for journey in journeys] 395 - passengers = Passenger.objects.filter(reduce(lambda x, y: x | y, conditions)) 503 + def brothers(self): 504 + """Gets the Passenger models from the same user to all the journeys of the 505 + recurrence. 506 + """ 507 + journeys = self.journey.brothers() 508 + journeys_pks = [journey.pk for journey in journeys] 509 + passengers = Passenger.objects.filter(journey__pk__in=journeys_pks) 396 510 return passengers 397 511 398 512 def has_recurrence(self): 399 513 """Check if there is more than one request for the recourrence of 400 514 the journey. 401 515 """ 402 - passengers = self.recurrence() 516 + passengers = self.brothers() 403 517 return passengers.count() > 1 404 518 405 519
+10 -1
upvcarshare/journeys/tests/factories.py
··· 28 28 model = "journeys.Campus" 29 29 30 30 31 - class JourneyFactory(factory.django.DjangoModelFactory): 31 + class JourneyTemplateFactory(factory.django.DjangoModelFactory): 32 32 user = factory.SubFactory(UserFactory) 33 33 residence = factory.SubFactory(ResidenceFactory) 34 34 campus = factory.SubFactory(CampusFactory) ··· 36 36 arrival = factory.LazyFunction(lambda: timezone.now() + datetime.timedelta(days=1, minutes=30)) 37 37 time_window = DEFAULT_TIME_WINDOW 38 38 kind = GOING 39 + 40 + class Meta: 41 + model = "journeys.JourneyTemplate" 42 + 43 + 44 + class JourneyFactory(factory.django.DjangoModelFactory): 45 + template = factory.SubFactory(JourneyTemplateFactory) 46 + departure = factory.LazyFunction(lambda: timezone.now() + datetime.timedelta(days=1)) 47 + arrival = factory.LazyFunction(lambda: timezone.now() + datetime.timedelta(days=1, minutes=30)) 39 48 40 49 class Meta: 41 50 model = "journeys.Journey"
+33 -17
upvcarshare/journeys/tests/test_api.py
··· 10 10 11 11 from journeys import GOING, RETURN, DEFAULT_PROJECTED_SRID 12 12 from journeys.models import Transport, Journey 13 - from journeys.tests.factories import TransportFactory, ResidenceFactory, CampusFactory, JourneyFactory, MessageFactory 13 + from journeys.tests.factories import TransportFactory, ResidenceFactory, CampusFactory, JourneyFactory, MessageFactory, \ 14 + JourneyTemplateFactory 14 15 from users.tests.factories import UserFactory 15 16 from users.tests.mocks import UPVLoginDataService 16 17 ··· 85 86 user = UserFactory() 86 87 origin = ResidenceFactory(user=user) 87 88 destination = CampusFactory() 88 - return JourneyFactory(user=user, residence=origin, campus=destination, kind=kind) 89 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination, kind=kind) 90 + return JourneyFactory(template=template, departure=template.departure, arrival=template.arrival) 89 91 90 92 def test_get_journeys(self): 91 93 user = UserFactory() ··· 177 179 campus = CampusFactory() 178 180 user3 = UserFactory() 179 181 residence3 = ResidenceFactory(user=user3, position=Point(865621.24, 545274.90, srid=DEFAULT_PROJECTED_SRID)) 180 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus) 181 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 182 - JourneyFactory(user=user3, driver=user3, residence=residence3, campus=campus) 182 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus) 183 + JourneyFactory(template=template) 184 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 185 + JourneyFactory(template=template) 186 + template = JourneyTemplateFactory(user=user3, driver=user3, residence=residence3, campus=campus) 187 + JourneyFactory(template=template) 183 188 user4 = UserFactory() 184 189 residence4 = ResidenceFactory( 185 190 user=user4, position=Point(882532.74, 545437.43, srid=DEFAULT_PROJECTED_SRID), distance=2500 186 191 ) 187 - JourneyFactory(user=user4, residence=residence4, campus=campus) 192 + template = JourneyTemplateFactory(user=user4, residence=residence4, campus=campus) 193 + JourneyFactory(template=template) 188 194 # Make query... 189 195 url = "/api/v1/journeys/recommended/" 190 196 self.client.force_authenticate(user=user4) ··· 202 208 campus = CampusFactory() 203 209 user3 = UserFactory() 204 210 residence3 = ResidenceFactory(user=user3, position=Point(865621.24, 545274.90, srid=DEFAULT_PROJECTED_SRID)) 205 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus) 206 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 207 - JourneyFactory(user=user3, driver=user3, residence=residence3, campus=campus) 211 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus) 212 + JourneyFactory(template=template) 213 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 214 + JourneyFactory(template=template) 215 + template = JourneyTemplateFactory(user=user3, driver=user3, residence=residence3, campus=campus) 216 + JourneyFactory(template=template) 208 217 user4 = UserFactory() 209 218 residence4 = ResidenceFactory( 210 219 user=user4, position=Point(882532.74, 545437.43, srid=DEFAULT_PROJECTED_SRID), distance=2500 211 220 ) 212 - journey = JourneyFactory(user=user4, residence=residence4, campus=campus) 221 + template = JourneyTemplateFactory(user=user4, residence=residence4, campus=campus) 222 + journey = JourneyFactory(template=template) 213 223 # Make query... 214 224 url = "/api/v1/journeys/{}/recommended/".format(journey.pk) 215 225 self.client.force_authenticate(user=user4) ··· 226 236 user = UserFactory() 227 237 origin = ResidenceFactory(user=user) 228 238 destination = CampusFactory() 229 - journey = JourneyFactory(user=user, residence=origin, campus=destination) 239 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination) 240 + journey = JourneyFactory(template=template) 230 241 [MessageFactory( 231 242 user=UserFactory(), 232 - journey=JourneyFactory(user=UserFactory(), residence=origin, campus=destination)) 243 + journey=JourneyFactory(template=JourneyTemplateFactory(user=UserFactory(), residence=origin, campus=destination))) 233 244 for _ in range(2)] 234 245 [MessageFactory(user=user, journey=journey) for _ in range(5)] 235 246 [MessageFactory(user=UserFactory(), journey=journey) for _ in range(5)] ··· 244 255 user = UserFactory() 245 256 origin = ResidenceFactory(user=user) 246 257 destination = CampusFactory() 247 - journey = JourneyFactory(user=user, residence=origin, campus=destination) 258 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination) 259 + journey = JourneyFactory(template=template) 248 260 [MessageFactory(user=user, journey=journey) for _ in range(5)] 249 261 [MessageFactory(user=UserFactory(), journey=journey) for _ in range(5)] 250 262 url = "/api/v1/journeys/{}/messages/".format(journey.pk) ··· 258 270 user = UserFactory() 259 271 origin = ResidenceFactory(user=user) 260 272 destination = CampusFactory() 261 - journey = JourneyFactory(user=user, residence=origin, campus=destination) 273 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination) 274 + journey = JourneyFactory(template=template) 262 275 self.assertEquals(0, journey.messages.count()) 263 276 data = { 264 277 "content": "Hello!", ··· 274 287 user = UserFactory() 275 288 origin = ResidenceFactory(user=user) 276 289 destination = CampusFactory() 277 - journey1 = JourneyFactory(user=user, residence=origin, campus=destination) 278 - journey2 = JourneyFactory(user=user, residence=origin, campus=destination) 279 - journey3 = JourneyFactory(user=UserFactory(), residence=origin, campus=destination) 290 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination) 291 + journey1 = JourneyFactory(template=template) 292 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination) 293 + journey2 = JourneyFactory(template=template) 294 + template = JourneyTemplateFactory(user=UserFactory(), residence=origin, campus=destination) 295 + journey3 = JourneyFactory(template=template) 280 296 [MessageFactory(user=user, journey=journey1) for _ in range(2)] 281 297 [MessageFactory(user=UserFactory(), journey=journey1) for _ in range(2)] 282 298 [MessageFactory(user=user, journey=journey2) for _ in range(2)]
+11 -4
upvcarshare/journeys/tests/test_forms.py
··· 4 4 import datetime 5 5 6 6 import six 7 + from django.utils.timezone import make_aware 7 8 from test_plus import TestCase 8 9 9 10 from journeys import DEFAULT_PROJECTED_SRID, DEFAULT_GOOGLE_MAPS_SRID 10 11 from journeys.forms import SearchJourneyForm 11 12 from journeys.helpers import make_point 12 - from journeys.tests.factories import JourneyFactory 13 + from journeys.tests.factories import JourneyFactory, JourneyTemplateFactory 13 14 from users.tests.factories import UserFactory 14 15 from users.tests.mocks import UPVLoginDataService 15 16 ··· 24 25 25 26 def test_search_form(self): 26 27 user = UserFactory() 27 - JourneyFactory(departure=datetime.datetime.now() + datetime.timedelta(days=2), user=user, driver=user) 28 - journey = JourneyFactory(user=user, driver=user) 28 + template = JourneyTemplateFactory( 29 + departure=make_aware(datetime.datetime.now() + datetime.timedelta(days=2)), 30 + user=user, 31 + driver=user 32 + ) 33 + JourneyFactory(template=template, departure=template.departure) 34 + template = JourneyTemplateFactory(user=user, driver=user) 35 + journey = JourneyFactory(template=template) 29 36 30 37 data = { 31 38 "departure_date": journey.departure.date(), ··· 36 43 journey.residence.position, 37 44 origin_coord_srid=DEFAULT_PROJECTED_SRID, 38 45 destiny_coord_srid=DEFAULT_GOOGLE_MAPS_SRID 39 - )) 46 + )), 40 47 } 41 48 form = SearchJourneyForm(data) 42 49 self.assertTrue(form.is_valid())
+16 -19
upvcarshare/journeys/tests/test_helpers.py
··· 4 4 import datetime 5 5 6 6 import recurrence 7 + from dateutil.rrule import rrule, DAILY 7 8 from django.contrib.gis.geos import Point 8 9 from django.utils.timezone import make_naive 9 10 from test_plus import TestCase ··· 11 12 from journeys import DEFAULT_PROJECTED_SRID, DEFAULT_WGS84_SRID 12 13 from journeys.helpers import make_point_wgs84, make_point_projected, date_to_datetime, first_day_current_week, \ 13 14 last_day_current_week, expand 14 - from journeys.tests.factories import JourneyFactory 15 + from journeys.tests.factories import JourneyFactory, JourneyTemplateFactory 15 16 from users.tests.mocks import UPVLoginDataService 16 17 17 18 try: ··· 44 45 self.assertIsInstance(last_day_current_week(), datetime.date) 45 46 46 47 def test_expand(self): 47 - journey = JourneyFactory() 48 - journey.departure = journey.departure.replace(month=1) 48 + journey = JourneyTemplateFactory() 49 + journey.departure = journey.departure.replace(day=1, month=1) 49 50 journey.save() 50 - 51 - rule = recurrence.Rule(recurrence.DAILY) 52 - pattern = recurrence.Recurrence( 53 - dtstart=make_naive(journey.departure) + datetime.timedelta(days=1), 54 - dtend=make_naive(journey.departure) + datetime.timedelta(days=20), 55 - rrules=[rule, ] 51 + pattern = rrule( 52 + dtstart=make_naive(journey.departure), 53 + until=make_naive(journey.departure) + datetime.timedelta(days=20), 54 + freq=DAILY 56 55 ) 57 - 58 - journey.recurrence = pattern 56 + journey.recurrence = str(pattern) 59 57 journey.save() 60 58 journeys = expand(journey) 61 - self.assertEquals(22, len(journeys)) 59 + self.assertEquals(21, len(journeys)) 62 60 63 61 def test_expand_september(self): 64 - journey = JourneyFactory() 62 + journey = JourneyTemplateFactory() 65 63 journey.departure = journey.departure.replace(day=1, month=9) 66 64 journey.save() 67 65 68 - rule = recurrence.Rule(recurrence.DAILY) 69 - pattern = recurrence.Recurrence( 66 + pattern = rrule( 70 67 dtstart=make_naive(journey.departure) + datetime.timedelta(days=1), 71 - dtend=make_naive(journey.departure) + datetime.timedelta(days=20), 72 - rrules=[rule, ] 68 + until=make_naive(journey.departure) + datetime.timedelta(days=20), 69 + freq=DAILY 73 70 ) 74 71 75 - journey.recurrence = pattern 72 + journey.recurrence = str(pattern) 76 73 journey.save() 77 74 journeys = expand(journey) 78 - self.assertEquals(22, len(journeys)) 75 + self.assertEquals(20, len(journeys))
+62 -30
upvcarshare/journeys/tests/test_models.py
··· 10 10 11 11 from journeys import GOING, RETURN, DEFAULT_PROJECTED_SRID 12 12 from journeys.exceptions import AlreadyAPassenger, NotAPassenger 13 - from journeys.models import Journey, Passenger, Residence 14 - from journeys.tests.factories import ResidenceFactory, CampusFactory, TransportFactory, JourneyFactory 13 + from journeys.helpers import expand 14 + from journeys.models import Journey, Passenger, Residence, JourneyTemplate 15 + from journeys.tests.factories import ResidenceFactory, CampusFactory, TransportFactory, JourneyFactory, \ 16 + JourneyTemplateFactory 15 17 from users.tests.factories import UserFactory 16 18 from users.tests.mocks import UPVLoginDataService 17 19 ··· 30 32 user = UserFactory() 31 33 origin = ResidenceFactory(user=user) 32 34 destination = CampusFactory() 33 - return JourneyFactory(user=user, residence=origin, campus=destination, kind=kind) 35 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination, kind=kind) 36 + return JourneyFactory(template=template) 34 37 35 38 def test_smart_create_no_transport(self): 36 39 """Test smart create of a journey without transport.""" 37 40 user = UserFactory() 38 41 origin = ResidenceFactory(user=user) 39 42 destination = CampusFactory() 40 - journey = Journey.objects.smart_create( 43 + template = JourneyTemplate.objects.smart_create( 41 44 user=user, origin=origin, destination=destination, departure=timezone.now() + datetime.timedelta(days=1) 42 45 ) 46 + journeys = expand(template) 47 + journey = journeys[0] 43 48 self.assertEquals(Journey.objects.count(), 1) 44 49 self.assertEquals(Journey.objects.first(), journey) 45 50 ··· 49 54 transport = TransportFactory(user=user) 50 55 origin = ResidenceFactory(user=user) 51 56 destination = CampusFactory() 52 - journey = Journey.objects.smart_create( 57 + template = JourneyTemplate.objects.smart_create( 53 58 user=user, origin=origin, destination=destination, departure=timezone.now() + datetime.timedelta(days=1), 54 59 transport=transport 55 60 ) 61 + journeys = expand(template) 62 + journey = journeys[0] 56 63 self.assertEquals(Journey.objects.count(), 1) 57 64 self.assertEquals(Journey.objects.first(), journey) 58 65 self.assertEquals(journey.free_places, transport.default_places) ··· 64 71 user = UserFactory() 65 72 origin = ResidenceFactory(user=user) 66 73 destination = CampusFactory() 67 - journey = JourneyFactory(user=user, residence=origin, campus=destination, kind=GOING) 74 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination, kind=GOING) 75 + journey = JourneyFactory(template=template) 68 76 self.assertEquals(journey.origin, origin) 69 77 70 78 def test_destination(self): 71 79 user = UserFactory() 72 80 origin = ResidenceFactory(user=user) 73 81 destination = CampusFactory() 74 - journey = JourneyFactory(user=user, residence=origin, campus=destination, kind=GOING) 82 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination, kind=GOING) 83 + journey = JourneyFactory(template=template) 75 84 self.assertEquals(journey.destination, destination) 76 85 77 86 def test_join_passenger(self): ··· 131 140 user2 = UserFactory() 132 141 residence2 = ResidenceFactory(user=user2, position=Point(882823.07, 545542.48, srid=DEFAULT_PROJECTED_SRID)) 133 142 campus = CampusFactory() 134 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus) 135 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 143 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus) 144 + JourneyFactory(template=template) 145 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 146 + JourneyFactory(template=template) 136 147 # Creates a journey without driver 137 148 user3 = UserFactory() 138 149 residence3 = ResidenceFactory(user=user3, position=Point(882454.58, 545877.33, srid=DEFAULT_PROJECTED_SRID)) 139 - JourneyFactory(user=user3, residence=residence3, campus=campus) 150 + template = JourneyTemplateFactory(user=user3, residence=residence3, campus=campus) 151 + JourneyFactory(template=template) 140 152 self.assertEquals(Journey.objects.available(user=user3, kind=GOING).count(), 2) 141 153 142 154 def test_available_return_query(self): ··· 146 158 user2 = UserFactory() 147 159 residence2 = ResidenceFactory(user=user2, position=Point(882823.07, 545542.48, srid=DEFAULT_PROJECTED_SRID)) 148 160 campus = CampusFactory() 149 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus, kind=RETURN) 150 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 161 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus, kind=RETURN) 162 + JourneyFactory(template=template) 163 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 164 + JourneyFactory(template=template) 151 165 # Creates a journey without driver 152 166 user3 = UserFactory() 153 167 residence3 = ResidenceFactory(user=user3, position=Point(865621.24, 545877.33, srid=DEFAULT_PROJECTED_SRID)) 154 - JourneyFactory(user=user3, residence=residence3, campus=campus, kind=RETURN) 168 + template = JourneyTemplateFactory(user=user3, residence=residence3, campus=campus, kind=RETURN) 169 + JourneyFactory(template=template) 155 170 self.assertEquals(Journey.objects.available(user=user3, kind=RETURN).count(), 1) 156 171 157 172 def test_available_query(self): ··· 161 176 user2 = UserFactory() 162 177 residence2 = ResidenceFactory(user=user2, position=Point(882823.07, 545542.48, srid=DEFAULT_PROJECTED_SRID)) 163 178 campus = CampusFactory() 164 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus, kind=RETURN) 165 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 179 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus, kind=RETURN) 180 + JourneyFactory(template=template) 181 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 182 + JourneyFactory(template=template) 166 183 # Creates a journey without driver 167 184 user3 = UserFactory() 168 185 residence3 = ResidenceFactory(user=user3, position=Point(865621.24, 545274.90, srid=DEFAULT_PROJECTED_SRID)) 169 - JourneyFactory(user=user3, residence=residence3, campus=campus, kind=RETURN) 186 + template = JourneyTemplateFactory(user=user3, residence=residence3, campus=campus, kind=RETURN) 187 + JourneyFactory(template=template) 170 188 self.assertEquals(Journey.objects.available(user=user3).count(), 2) 171 189 172 190 def test_nearby_query(self): ··· 178 196 campus = CampusFactory() 179 197 user3 = UserFactory() 180 198 residence3 = ResidenceFactory(user=user3, position=Point(865621.24, 545274.90, srid=DEFAULT_PROJECTED_SRID)) 181 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus) 182 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 183 - JourneyFactory(user=user3, driver=user3, residence=residence3, campus=campus) 199 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus) 200 + JourneyFactory(template=template) 201 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 202 + JourneyFactory(template=template) 203 + template = JourneyTemplateFactory(user=user3, driver=user3, residence=residence3, campus=campus) 204 + JourneyFactory(template=template) 184 205 point = Point(882532.74, 545437.43, srid=DEFAULT_PROJECTED_SRID) 185 206 self.assertEquals(Journey.objects.nearby( 186 207 geometry=point, ··· 196 217 campus = CampusFactory() 197 218 user3 = UserFactory() 198 219 residence3 = ResidenceFactory(user=user3, position=Point(865621.24, 545274.90, srid=DEFAULT_PROJECTED_SRID)) 199 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus) 200 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 201 - JourneyFactory(user=user3, driver=user3, residence=residence3, campus=campus) 220 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus) 221 + JourneyFactory(template=template) 222 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 223 + JourneyFactory(template=template) 224 + template = JourneyTemplateFactory(user=user3, driver=user3, residence=residence3, campus=campus) 225 + JourneyFactory(template=template) 202 226 point = Point(882532.74, 545437.43, srid=DEFAULT_PROJECTED_SRID) 203 227 self.assertEquals(Journey.objects.nearby( 204 228 geometry=point, ··· 215 239 campus = CampusFactory() 216 240 user3 = UserFactory() 217 241 residence3 = ResidenceFactory(user=user3, position=Point(865621.24, 545274.90, srid=DEFAULT_PROJECTED_SRID)) 218 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus) 219 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 220 - JourneyFactory(user=user3, driver=user3, residence=residence3, campus=campus) 242 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus) 243 + JourneyFactory(template=template) 244 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 245 + JourneyFactory(template=template) 246 + template = JourneyTemplateFactory(user=user3, driver=user3, residence=residence3, campus=campus) 247 + JourneyFactory(template=template) 221 248 user4 = UserFactory() 222 249 residence4 = ResidenceFactory( 223 250 user=user4, position=Point(882532.74, 545437.43, srid=DEFAULT_PROJECTED_SRID), distance=2500 224 251 ) 225 - JourneyFactory(user=user4, residence=residence4, campus=campus) 252 + template = JourneyTemplateFactory(user=user4, residence=residence4, campus=campus) 253 + JourneyFactory(template=template) 226 254 self.assertEquals(Journey.objects.recommended( 227 255 user=user4, 228 256 ).count(), 2) ··· 236 264 campus = CampusFactory() 237 265 user3 = UserFactory() 238 266 residence3 = ResidenceFactory(user=user3, position=Point(865621.24, 545274.90, srid=DEFAULT_PROJECTED_SRID)) 239 - JourneyFactory(user=user1, driver=user1, residence=residence1, campus=campus) 240 - JourneyFactory(user=user2, driver=user2, residence=residence2, campus=campus) 241 - JourneyFactory(user=user3, driver=user3, residence=residence3, campus=campus) 267 + template = JourneyTemplateFactory(user=user1, driver=user1, residence=residence1, campus=campus) 268 + JourneyFactory(template=template) 269 + template = JourneyTemplateFactory(user=user2, driver=user2, residence=residence2, campus=campus) 270 + JourneyFactory(template=template) 271 + template = JourneyTemplateFactory(user=user3, driver=user3, residence=residence3, campus=campus) 272 + JourneyFactory(template=template) 242 273 user4 = UserFactory() 243 274 residence4 = ResidenceFactory( 244 275 user=user4, position=Point(882532.74, 545437.43, srid=DEFAULT_PROJECTED_SRID), distance=2500 245 276 ) 246 - JourneyFactory(user=user4, residence=residence4, campus=campus) 277 + template = JourneyTemplateFactory(user=user4, residence=residence4, campus=campus) 278 + JourneyFactory(template=template) 247 279 self.assertEquals(Journey.objects.recommended( 248 280 user=user4, 249 281 kind=GOING
+32 -24
upvcarshare/journeys/tests/test_views.py
··· 8 8 9 9 from journeys import GOING, RETURN 10 10 from journeys.models import Journey, Residence, Passenger 11 - from journeys.tests.factories import JourneyFactory, ResidenceFactory, CampusFactory 11 + from journeys.tests.factories import JourneyFactory, ResidenceFactory, CampusFactory, JourneyTemplateFactory 12 12 from users.tests.factories import UserFactory 13 13 from users.tests.mocks import UPVLoginDataService 14 14 ··· 99 99 100 100 def test_get_edit_journey(self): 101 101 user = self.make_user(username="foo") 102 - journey = JourneyFactory(user=user) 102 + journey = JourneyFactory(template=JourneyTemplateFactory(user=user)) 103 103 url_name = "journeys:edit" 104 104 self.assertLoginRequired(url_name, pk=journey.pk) 105 105 with self.login(user): ··· 108 108 109 109 def test_post_edit_journey(self): 110 110 user = self.make_user(username="foo") 111 - journey = JourneyFactory(user=user, kind=GOING) 111 + template = JourneyTemplateFactory(user=user, kind=GOING) 112 + journey = JourneyFactory(template=template) 112 113 url_name = "journeys:edit" 113 114 self.assertLoginRequired(url_name, pk=journey.pk) 114 115 with self.login(user): 115 116 data = { 116 - "residence": ResidenceFactory(user=user).pk, 117 - "campus": CampusFactory().pk, 118 - "kind": RETURN, 119 - "free_places": 4, 120 - "time_window": 30, 117 + # "residence": ResidenceFactory(user=user).pk, 118 + # "campus": CampusFactory().pk, 119 + # "kind": RETURN, 120 + "free_places": 3, 121 + # "time_window": 30, 121 122 "departure": (timezone.now() + datetime.timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S"), 122 123 "recurrence": "", 123 124 } 124 125 response = self.post(url_name=url_name, pk=journey.pk, data=data) 125 126 self.response_302(response=response) 126 127 journey = Journey.objects.get(pk=journey.pk) 127 - self.assertEquals(RETURN, journey.kind) 128 + self.assertEquals(data["free_places"], journey.free_places) 128 129 129 130 def test_get_recommended_journey(self): 130 131 user = self.make_user(username="foo") ··· 152 153 153 154 def test_post_join(self): 154 155 user = self.make_user(username="foo") 155 - journey = JourneyFactory(user=user, kind=GOING) 156 + template = JourneyTemplateFactory(user=user, kind=GOING) 157 + journey = JourneyFactory(template=template) 156 158 url_name = "journeys:join" 157 159 self.assertLoginRequired(url_name, pk=journey.pk) 158 160 with self.login(user): ··· 161 163 162 164 def test_post_join_recurrence_all(self): 163 165 user = self.make_user(username="foo") 164 - journey = JourneyFactory(user=user, kind=GOING) 165 - journeys = [JourneyFactory(user=user, kind=GOING, parent=journey) for _ in range(10)] 166 + template = JourneyTemplateFactory(user=user, kind=GOING) 167 + journey = JourneyFactory(template=template) 168 + journeys = [JourneyFactory(template=template) for _ in range(10)] 166 169 url_name = "journeys:join" 167 170 self.assertLoginRequired(url_name, pk=journey.pk) 168 171 with self.login(user): ··· 175 178 176 179 def test_post_join_recurrence_one(self): 177 180 user = self.make_user(username="foo") 178 - journey = JourneyFactory(user=user, kind=GOING) 179 - [JourneyFactory(user=user, kind=GOING, parent=journey) for _ in range(10)] 181 + template = JourneyTemplateFactory(user=user, kind=GOING) 182 + journey = JourneyFactory(template=template) 183 + [JourneyFactory(template=template) for _ in range(10)] 180 184 url_name = "journeys:join" 181 185 self.assertLoginRequired(url_name, pk=journey.pk) 182 186 with self.login(user): ··· 189 193 190 194 def test_post_leave(self): 191 195 user = self.make_user(username="foo") 192 - journey = JourneyFactory(user=user, kind=GOING) 196 + journey = JourneyFactory(template=JourneyTemplateFactory(user=user)) 193 197 journey.join_passenger(user) 194 198 url_name = "journeys:leave" 195 199 self.assertLoginRequired(url_name, pk=journey.pk) ··· 199 203 200 204 def test_post_throw_out(self): 201 205 user = self.make_user(username="foo") 202 - journey = JourneyFactory(user=user, kind=GOING) 206 + journey = JourneyFactory(template=JourneyTemplateFactory(user=user)) 203 207 passenger = journey.join_passenger(user) 204 208 url_name = "journeys:throw-out" 205 209 self.assertLoginRequired(url_name, pk=passenger.pk) ··· 209 213 210 214 def test_cancel_journey(self): 211 215 user = self.make_user(username="foo") 212 - journey = JourneyFactory(user=user) 216 + template = JourneyTemplateFactory(user=user) 217 + journey = JourneyFactory(template=template) 213 218 url_name = "journeys:cancel" 214 219 self.assertLoginRequired(url_name, pk=journey.pk) 215 220 with self.login(user): ··· 218 223 219 224 def test_post_cancel_journey(self): 220 225 user = self.make_user(username="foo") 221 - journey = JourneyFactory(user=user) 226 + template = JourneyTemplateFactory(user=user) 227 + journey = JourneyFactory(template=template) 222 228 url_name = "journeys:cancel" 223 229 self.assertLoginRequired(url_name, pk=journey.pk) 224 230 with self.login(user): ··· 228 234 229 235 def test_delete_journey(self): 230 236 user = self.make_user(username="foo") 231 - journeys = [JourneyFactory(user=user) for _ in range(10)] 232 - journey = JourneyFactory(user=user) 237 + journeys = [JourneyFactory(template=JourneyTemplateFactory(user=user)) for _ in range(10)] 238 + journey = JourneyFactory(template=JourneyTemplateFactory(user=user)) 233 239 url_name = "journeys:delete" 234 240 self.assertLoginRequired(url_name, pk=journey.pk) 235 241 self.assertEquals(len(journeys) + 1, Journey.objects.count()) ··· 241 247 242 248 def test_delete_parent_journeys(self): 243 249 user = self.make_user(username="foo") 244 - journey = JourneyFactory(user=user) 245 - journeys = [JourneyFactory(user=user, parent=journey) for _ in range(10)] 250 + template = JourneyTemplateFactory(user=user) 251 + journey = JourneyFactory(template=template) 252 + journeys = [JourneyFactory(template=template) for _ in range(10)] 246 253 url_name = "journeys:delete" 247 254 self.assertLoginRequired(url_name, pk=journey.pk) 248 255 self.assertEquals(len(journeys) + 1, Journey.objects.count()) ··· 254 261 255 262 def test_delete_all_journeys(self): 256 263 user = self.make_user(username="foo") 257 - journey = JourneyFactory(user=user) 258 - journeys = [JourneyFactory(user=user, parent=journey) for _ in range(10)] 264 + template = JourneyTemplateFactory(user=user) 265 + journey = JourneyFactory(template=template) 266 + journeys = [JourneyFactory(template=template) for _ in range(10)] 259 267 other_journeys = [JourneyFactory() for _ in range(5)] 260 268 url_name = "journeys:delete-all" 261 269 self.assertLoginRequired(url_name, pk=journey.pk)
+46 -31
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 14 + from django.utils.timezone import make_naive 15 + from django.utils.translation import ugettext_lazy as _ 13 16 from django.views.generic import View 14 - from django.utils.translation import ugettext_lazy as _ 15 - from django.utils.timezone import make_naive 16 17 17 18 from journeys import GOING 18 19 from journeys.exceptions import AlreadyAPassenger, NoFreePlaces, NotAPassenger 19 - from journeys.forms import SmartJourneyForm, JourneyForm, FilterForm, \ 20 - ConfirmRejectJourneyForm, SearchJourneyForm 20 + from journeys.forms import SmartJourneyTemplateForm, JourneyForm, FilterForm, \ 21 + ConfirmRejectJourneyForm, SearchJourneyForm 21 22 from journeys.models import Residence, Campus, Journey, Passenger 22 23 23 24 ··· 25 26 """View to show journey creation form and to handle its creation.""" 26 27 27 28 template_name = "journeys/create.smart.html" 28 - form = SmartJourneyForm 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 } 51 + # Warnings 52 + if request.user.transports.count() == 0: 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 + ))) 58 + if request.user.residences.count() == 0: 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 + ))) 45 64 return render(request, self.template_name, data) 46 65 47 66 def post(self, request): 48 - form = self.form(request.POST, user=request.user) 67 + form = self.form(request.POST, user=request.user, initial=self._initial_values(request)) 49 68 data = { 50 69 "form": form 51 70 } 52 71 if form.is_valid(): 53 - journey = form.save() 54 - 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) 55 75 return render(request, self.template_name, data) 56 76 57 77 ··· 61 81 template_name = "journeys/edit.html" 62 82 63 83 def get(self, request, pk): 64 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 84 + journey = get_object_or_404(Journey, pk=pk, template__user=request.user) 65 85 form = JourneyForm( 66 86 instance=journey, 67 87 initial={"i_am_driver": journey.driver is not None and journey.driver == request.user}, ··· 74 94 return render(request, self.template_name, data) 75 95 76 96 def post(self, request, pk): 77 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 97 + journey = get_object_or_404(Journey, pk=pk, template__user=request.user) 78 98 form = JourneyForm(request.POST, instance=journey, user=request.user) 79 99 data = { 80 100 "form": form, ··· 113 133 "fulfilled_by": journey.fulfilled_by(), 114 134 "passengers": journey.passengers_list(request.user), 115 135 "recommended": journey.recommended(), 116 - "has_recurrence": journey.parent is not None or journey.children.exists() 136 + "has_recurrence": journey.has_recurrence 117 137 } 118 138 return render(request, self.template_name, data) 119 139 ··· 145 165 template_name = "journeys/list.html" 146 166 147 167 def get(self, request): 148 - journeys = Journey.objects.filter(user=request.user).order_by("departure") 168 + journeys = Journey.objects.filter(template__user=request.user).order_by("departure") 149 169 data = { 150 170 "journeys": journeys, 151 171 "journeys_count": journeys.count() ··· 220 240 221 241 def post(self, request, pk): 222 242 journey = get_object_or_404(Journey, pk=pk) 243 + leave_from = request.POST.get("leave_from") 223 244 try: 224 - journey.leave_passenger(request.user) 225 - messages.success(request, _('Has dejado el viaje')) 245 + journey.leave_passenger(request.user, leave_from=leave_from) 246 + if leave_from == "one": 247 + messages.success(request, _('Has dejado el viaje')) 248 + elif leave_from == "all": 249 + messages.success(request, _('Has dejado todos los viajes')) 226 250 except NotAPassenger: 227 251 messages.success(request, _('No estás en este viaje')) 228 252 return_to = request.POST.get("return_to", self.return_to) ··· 269 293 template_name = "journeys/cancel.html" 270 294 271 295 def get(self, request, pk): 272 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 296 + journey = get_object_or_404(Journey, pk=pk, template__user=request.user) 273 297 data = { 274 298 "journey": journey, 275 299 } ··· 277 301 278 302 @staticmethod 279 303 def post(request, pk): 280 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 304 + journey = get_object_or_404(Journey, pk=pk, template__user=request.user) 281 305 journey.cancel() 282 306 return redirect("journeys:details", pk=journey.pk) 283 307 ··· 287 311 288 312 @staticmethod 289 313 def get(request, pk): 290 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 314 + journey = get_object_or_404(Journey, pk=pk, template__user=request.user) 291 315 # Delete only if the journey hasn't driver 292 316 if journey.driver is None: 293 - if journey.has_recurrence and journey.children.exists(): 294 - new_parent = journey.children.order_by("departure").first() 295 - journey.children.exclude(pk=new_parent.pk).update(parent=new_parent) 296 - new_parent.parent = None 297 - new_parent.save() 298 - # Get again the journey 299 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 300 317 journey.delete() 301 318 messages.success(request, _('Has borrado el viaje')) 302 319 return redirect("journeys:list") ··· 309 326 310 327 @staticmethod 311 328 def get(request, pk): 312 - journey = get_object_or_404(Journey, pk=pk, user=request.user) 329 + journey = get_object_or_404(Journey, pk=pk, template__user=request.user) 313 330 # Delete only if the journey hasn't driver 314 331 if journey.driver is None: 315 332 if journey.has_recurrence: 316 - if journey.parent is not None: 317 - journeys = journey.parent.children.filter(departure__gte=journey.departure) 318 - journeys.delete() 319 - else: 320 - journey.delete() 333 + # If has recurrente, delete all future brothers 334 + journeys = journey.brothers().filter(departure__gte=journey.departure) 335 + journeys.delete() 321 336 else: 322 337 journey.delete() 323 338 messages.success(request, _('Has borrado el viaje y sus repeticiones'))
+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,
+4 -2
upvcarshare/notifications/tests/test_api.py
··· 8 8 from rest_framework.test import APITestCase 9 9 10 10 from journeys import GOING 11 - from journeys.tests.factories import CampusFactory, JourneyFactory 11 + from journeys.tests.factories import CampusFactory, JourneyFactory, JourneyTemplateFactory 12 12 from journeys.tests.factories import ResidenceFactory 13 13 from notifications import JOIN 14 14 from notifications import LEAVE ··· 30 30 user = UserFactory() if user is None else user 31 31 origin = ResidenceFactory(user=user) 32 32 destination = CampusFactory() 33 - return JourneyFactory(user=user, residence=origin, campus=destination, kind=kind) 33 + return JourneyFactory( 34 + template=JourneyTemplateFactory(user=user, residence=origin, campus=destination, kind=kind) 35 + ) 34 36 35 37 def test_get_notifications(self): 36 38 user = UserFactory()
+3 -2
upvcarshare/notifications/tests/test_models.py
··· 4 4 from test_plus import TestCase 5 5 6 6 from journeys import GOING 7 - from journeys.tests.factories import CampusFactory 7 + from journeys.tests.factories import CampusFactory, JourneyTemplateFactory 8 8 from journeys.tests.factories import ResidenceFactory, JourneyFactory 9 9 from notifications import JOIN, LEAVE 10 10 from notifications.models import Notification ··· 25 25 user = UserFactory() if user is None else user 26 26 origin = ResidenceFactory(user=user) 27 27 destination = CampusFactory() 28 - return JourneyFactory(user=user, residence=origin, campus=destination, kind=kind) 28 + template = JourneyTemplateFactory(user=user, residence=origin, campus=destination, kind=kind) 29 + return JourneyFactory(template=template) 29 30 30 31 def test_join_generation(self): 31 32 initial_user = UserFactory()
+14
upvcarshare/static/src/js/journeys/components/rrules.js
··· 1 + import RRulesController from '../controllers/rrules'; 2 + 3 + 4 + const RRulesComponent = { 5 + controller: RRulesController, 6 + templateUrl: "/partials/journeys/rrules.html", 7 + bindings: { 8 + fieldName: '@', 9 + fieldId: '@', 10 + overrideDeparture: '<' 11 + } 12 + }; 13 + 14 + export default RRulesComponent;
+134
upvcarshare/static/src/js/journeys/controllers/rrules.js
··· 1 + // Controlle for RRule component 2 + import RRule from 'rrule'; 3 + import { SPANISH, gettext } from './rrules_i18n'; 4 + 5 + 6 + class RRulesController { 7 + 8 + constructor($scope) { 9 + this.$scope = $scope; 10 + } 11 + 12 + $onInit() { 13 + this.rrule = null; 14 + this.rruleString = null; 15 + this.rruleText = null; 16 + this.previousRRule = null; 17 + this.isActivated = false; 18 + this.dtstart = new Date(); 19 + 20 + this.freqOptions = [ 21 + {label: "Cada año", value: RRule.YEARLY}, 22 + {label: "Cada mes", value: RRule.MONTHLY}, 23 + {label: "Cada semana", value: RRule.WEEKLY}, 24 + {label: "Cada día", value: RRule.DAILY} 25 + ] 26 + 27 + this.byweekdayOptions = [ 28 + {label: "L", value: RRule.MO, model: false}, 29 + {label: "M", value: RRule.TU, model: false}, 30 + {label: "X", value: RRule.WE, model: false}, 31 + {label: "J", value: RRule.TH, model: false}, 32 + {label: "V", value: RRule.FR, model: false}, 33 + {label: "S", value: RRule.SA, model: false}, 34 + {label: "D", value: RRule.SU, model: false}, 35 + ] 36 + 37 + this.wkstOptions = [ 38 + {label: "L", value: RRule.MO, model: false}, 39 + {label: "M", value: RRule.TU, model: false}, 40 + {label: "X", value: RRule.WE, model: false}, 41 + {label: "J", value: RRule.TH, model: false}, 42 + {label: "V", value: RRule.FR, model: false}, 43 + {label: "S", value: RRule.SA, model: false}, 44 + {label: "D", value: RRule.SU, model: false}, 45 + ] 46 + 47 + this.intervalOptions = [...Array(29).keys()].map(x => x + 1); 48 + this.intervalLabel = null; 49 + 50 + this.ends = "never"; 51 + } 52 + 53 + $doCheck() { 54 + if (!this.isActivated) { 55 + this.previousRRule = this.rrule; 56 + this.rrule = null; 57 + } else if (this.rrule === null) { 58 + this.rrule = this.previousRRule !== null ? previousRRule : this.initialRRule(); 59 + } 60 + if (this.rrule !== null) { 61 + this.updateIntervalLabel(); 62 + this.updateByweekday(); 63 + this.updateEnds(); 64 + this.updateRRule(); 65 + } 66 + } 67 + 68 + $onChanges(changesObj) { 69 + this.dtstart = this.overrideDeparture; 70 + if (this.rrule !== null && this.rrule !== undefined) { 71 + this.rrule.dtstart = this.overrideDeparture; 72 + this.updateRRule(); 73 + } 74 + } 75 + 76 + updateEnds() { 77 + if (this.rrule !== null && this.rrule !== undefined) { 78 + if (this.ends == "never") { 79 + this.rrule.count = null; 80 + this.rrule.until = null; 81 + } else if (this.ends == "count") { 82 + this.rrule.until = null; 83 + if (this.rrule.count == null) this.rrule.count = 1; 84 + } else if (this.ends == "until") { 85 + this.rrule.count = null; 86 + } 87 + } 88 + } 89 + 90 + updateByweekday() { 91 + let byweekday = []; 92 + this.byweekdayOptions.map((option) => { 93 + if (option.model) { 94 + byweekday.push(option.value); 95 + } 96 + }) 97 + this.rrule.byweekday = byweekday; 98 + } 99 + 100 + updateIntervalLabel() { 101 + if (this.rrule.freq == RRule.YEARLY) this.intervalLabel = "años"; 102 + if (this.rrule.freq == RRule.MONTHLY) this.intervalLabel = "meses"; 103 + if (this.rrule.freq == RRule.WEEKLY) this.intervalLabel = "semanas"; 104 + if (this.rrule.freq == RRule.DAILY) this.intervalLabel = "días"; 105 + } 106 + 107 + updateRRule() { 108 + if (this.rrule !== null) { 109 + const rrule = new RRule(this.rrule); 110 + this.rruleString = rrule.toString(); 111 + this.rruleText = rrule.toText(gettext, SPANISH); 112 + } 113 + } 114 + 115 + initialRRule() { 116 + return { 117 + freq: this.freqOptions[2].value, 118 + byweekday: null, 119 + dtstart: this.dtstart, 120 + count: null, 121 + until: null 122 + } 123 + } 124 + hiddeByweekday() { 125 + if (this.rrule !== null) { 126 + return this.rrule.freq !== RRule.WEEKLY; 127 + } 128 + return false; 129 + } 130 + } 131 + 132 + RRulesController.$inject = ['$scope']; 133 + 134 + export default RRulesController;
+87
upvcarshare/static/src/js/journeys/controllers/rrules_i18n.js
··· 1 + // Translations for rrules.js 2 + 3 + export const SPANISH = { 4 + dayNames: [ 5 + 'domingo', 'lunes', 'martes', 'miércoles', 6 + 'jueves', 'viernes', 'sábado' 7 + ], 8 + monthNames: [ 9 + 'enero', 'febrero', 'marzo', 'abril', 'mayo', 10 + 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 11 + 'noviembre', 'diciembre' 12 + ], 13 + tokens: { 14 + 'SKIP': /^[ \r\n\t]+|^\.$/, 15 + 'number': /^[1-9][0-9]*/, 16 + 'numberAsText': /^(one|two|three)/i, 17 + 'every': /^every/i, 18 + 'day(s)': /^days?/i, 19 + 'weekday(s)': /^weekdays?/i, 20 + 'week(s)': /^weeks?/i, 21 + 'hour(s)': /^hours?/i, 22 + 'month(s)': /^months?/i, 23 + 'year(s)': /^years?/i, 24 + 'on': /^(on|in)/i, 25 + 'at': /^(at)/i, 26 + 'the': /^the/i, 27 + 'first': /^first/i, 28 + 'second': /^second/i, 29 + 'third': /^third/i, 30 + 'nth': /^([1-9][0-9]*)(\.|th|nd|rd|st)/i, 31 + 'last': /^last/i, 32 + 'for': /^for/i, 33 + 'time(s)': /^times?/i, 34 + 'until': /^(un)?til/i, 35 + 'monday': /^mo(n(day)?)?/i, 36 + 'tuesday': /^tu(e(s(day)?)?)?/i, 37 + 'wednesday': /^we(d(n(esday)?)?)?/i, 38 + 'thursday': /^th(u(r(sday)?)?)?/i, 39 + 'friday': /^fr(i(day)?)?/i, 40 + 'saturday': /^sa(t(urday)?)?/i, 41 + 'sunday': /^su(n(day)?)?/i, 42 + 'january': /^jan(uary)?/i, 43 + 'february': /^feb(ruary)?/i, 44 + 'march': /^mar(ch)?/i, 45 + 'april': /^apr(il)?/i, 46 + 'may': /^may/i, 47 + 'june': /^june?/i, 48 + 'july': /^july?/i, 49 + 'august': /^aug(ust)?/i, 50 + 'september': /^sep(t(ember)?)?/i, 51 + 'october': /^oct(ober)?/i, 52 + 'november': /^nov(ember)?/i, 53 + 'december': /^dec(ember)?/i, 54 + 'comma': /^(,\s*|(and|or)\s*)+/i 55 + } 56 + } 57 + 58 + const esStrings = { 59 + 'every': 'cada', 60 + 'day': 'día', 61 + 'days': 'días', 62 + 'weekday': 'día laborable', 63 + 'week': 'semana', 64 + 'weeks': 'semanas', 65 + 'hour': 'hora', 66 + 'hours': 'hora', 67 + 'month': 'mes', 68 + 'months': 'meses', 69 + 'year': 'año', 70 + 'years': 'años', 71 + 'on': 'los', 72 + 'at': 'en', 73 + 'the': 'el', 74 + 'first': 'primero', 75 + 'second': 'segundo', 76 + 'third': 'tercer', 77 + 'last': 'último', 78 + 'for': 'por', 79 + 'time': 'vez', 80 + 'times': 'veces', 81 + 'until': 'hasta', 82 + }; 83 + 84 + export function gettext(id) { 85 + // Returns es string, default to english 86 + return esStrings[id] || id; 87 + }
+24 -3
upvcarshare/static/src/js/journeys/index.js
··· 1 1 import angular from 'angular'; 2 2 import JourneyService from './journeys.service'; 3 - import {OriginDestinationSelectComponent, DatetimeComponent, DateComponent, TimeComponent, CalendarComponent, CircleMapComponent, RecurrenceCalendarComponent} from './journeys.component'; 4 - import {JourneyForm, JoinJourneyForm, SearchJourneyForm, ConfirmPassengerForm, 5 - RejectPassengerForm} from './journey.directive'; 3 + import { 4 + OriginDestinationSelectComponent, 5 + DatetimeComponent, 6 + DateComponent, 7 + TimeComponent, 8 + CalendarComponent, 9 + CircleMapComponent, 10 + RecurrenceCalendarComponent 11 + } from './journeys.component'; 12 + import RRulesComponent from './components/rrules'; 13 + import { 14 + JourneyForm, 15 + JoinJourneyForm, 16 + SearchJourneyForm, 17 + ConfirmPassengerForm, 18 + RejectPassengerForm, 19 + ResidenceForm, 20 + LeaveJourneyForm, 21 + ThrowPassengerForm 22 + } from './journey.directive'; 6 23 import JoinAllOneController from './journeys.controller'; 7 24 8 25 import 'lodash'; ··· 33 50 .component('calendar', CalendarComponent) 34 51 .component('circleMap', CircleMapComponent) 35 52 .component('recurrenceCalendar', RecurrenceCalendarComponent) 53 + .component('rRules', RRulesComponent) 36 54 37 55 .directive('journeyForm', JourneyForm) 56 + .directive('residenceForm', ResidenceForm) 38 57 .directive('searchJourneyForm', SearchJourneyForm) 39 58 .directive('joinJourneyForm', JoinJourneyForm) 59 + .directive('leaveJourneyForm', LeaveJourneyForm) 40 60 .directive('confirmPassengerForm', ConfirmPassengerForm) 61 + .directive('throwPassengerForm', ThrowPassengerForm) 41 62 .directive('rejectPassengerForm', RejectPassengerForm) 42 63 43 64 // Angular Google Maps
+89 -4
upvcarshare/static/src/js/journeys/journey.directive.js
··· 1 - import {JoinAllOneController, ConfirmRejectPassengerController} from './journeys.controller'; 1 + import { 2 + JoinAllOneController, 3 + ConfirmRejectPassengerController, 4 + LeaveAllOneController, 5 + ConfirmThrowPassengerController 6 + } from './journeys.controller'; 2 7 import moment from 'moment'; 3 8 4 9 ··· 21 26 link: (scope, element, attr) => { 22 27 scope.iAmDriver = "False"; 23 28 scope.newArrivalValue = null; 29 + scope.newDepartureValue = null; 24 30 25 31 scope.onUpdateDeparture = (value) => { 26 - // console.log("Updated departure: ", value); 27 - var newValue = moment(value).add(30, 'm'); 32 + scope.newDepartureValue = value; 33 + let newValue = moment(value).add(30, 'm'); 28 34 scope.newArrivalValue = newValue.toDate(); 29 35 }; 30 36 31 37 } 32 38 }); 33 39 40 + const ResidenceForm = () => ({ 41 + restrict: 'A', 42 + link: (scope, element, attr) => { 43 + scope.address = ""; 44 + scope.onUpdateAddress = (value) => { 45 + scope.address = value; 46 + } 47 + } 48 + }); 49 + 34 50 const JoinJourneyForm = ($uibModal) => ({ 35 51 restrict: 'A', 36 52 link: (scope, element, attr) => { ··· 74 90 }); 75 91 JoinJourneyForm.$inject = ["$uibModal"]; 76 92 93 + const LeaveJourneyForm = ($uibModal) => ({ 94 + restrict: 'A', 95 + link: (scope, element, attr) => { 96 + // Initial value for leave from value to one. It could be 'one' or 'all' 97 + scope.leaveFromValue = null; 98 + scope.journeyId = attr.journeyId; 99 + 100 + // Function to open modal 101 + function openModal() { 102 + var modalInstance = $uibModal.open({ 103 + animation: true, 104 + templateUrl: 'leave-all-one.html', 105 + controller: LeaveAllOneController, 106 + resolve: { 107 + journeyId: function () { 108 + return scope.journeyId; 109 + } 110 + } 111 + }); 112 + modalInstance.result.then( (selectedOption) => { 113 + scope.leaveFromValue = selectedOption; 114 + var field = element.find("[name='leave_from']"); 115 + field.val(selectedOption); 116 + element.submit(); 117 + }); 118 + } 119 + 120 + // Link on submit form 121 + element.submit(() => { 122 + if (scope.leaveFromValue == null) { 123 + openModal(); 124 + return false; 125 + } 126 + return true; 127 + }); 128 + } 129 + }); 130 + LeaveJourneyForm.$inject = ["$uibModal"]; 131 + 77 132 78 133 const ConfirmPassengerForm = ($uibModal) => ({ 79 134 restrict: 'A', ··· 107 162 }); 108 163 ConfirmPassengerForm.$inject = ["$uibModal"]; 109 164 165 + const ThrowPassengerForm = ($uibModal) => ({ 166 + restrict: 'A', 167 + link: (scope, element, attr) => { 168 + // Initial value for join to value to one. It could be 'one' or 'all' 169 + scope.confirmValue = null; 170 + 171 + // Function to open modal 172 + function openModal() { 173 + var modalInstance = $uibModal.open({ 174 + animation: true, 175 + templateUrl: 'throw-passenger.html', 176 + controller: ConfirmThrowPassengerController 177 + }); 178 + modalInstance.result.then( (selectedOption) => { 179 + scope.confirmValue = selectedOption; 180 + element.submit(); 181 + }); 182 + } 183 + 184 + // Link on submit form 185 + element.submit(() => { 186 + if (scope.confirmValue == null) { 187 + openModal(); 188 + return false; 189 + } 190 + return scope.confirmValue; 191 + }); 192 + } 193 + }); 194 + ThrowPassengerForm.$inject = ["$uibModal"]; 110 195 111 196 const RejectPassengerForm = ($uibModal) => ({ 112 197 restrict: 'A', ··· 142 227 143 228 144 229 export {JourneyForm, JoinJourneyForm, SearchJourneyForm, ConfirmPassengerForm, 145 - RejectPassengerForm}; 230 + RejectPassengerForm, ResidenceForm, LeaveJourneyForm, ThrowPassengerForm};
+13 -4
upvcarshare/static/src/js/journeys/journeys.component.js
··· 1 1 // Component for select origin and destination of a journey. 2 - import {OriginDestinationSelectController, DatetimeController, DateController, TimeController, CalendarController, CircleMapController, RecurrenceCalendarController} 3 - from './journeys.controller'; 2 + import { 3 + OriginDestinationSelectController, 4 + DatetimeController, 5 + DateController, 6 + TimeController, 7 + CalendarController, 8 + CircleMapController, 9 + RecurrenceCalendarController 10 + } from './journeys.controller'; 4 11 5 12 6 13 const OriginDestinationSelectComponent = { ··· 23 30 fieldName: '@', 24 31 fieldId: '@', 25 32 overrideValue: '<', 26 - onUpdate: '&' 33 + onUpdate: '&', 34 + master: '=' 27 35 } 28 36 }; 29 37 ··· 78 86 radiusFieldId: '@', 79 87 positionValue: '@', 80 88 positionField: '@', 81 - positionFieldId: '@' 89 + positionFieldId: '@', 90 + onUpdate: '&' 82 91 } 83 92 }; 84 93
+62 -12
upvcarshare/static/src/js/journeys/journeys.controller.js
··· 94 94 } 95 95 96 96 $onInit() { 97 - var date = this.value !== undefined ? moment(this.value).toDate() : new Date(); 97 + const date = this.value !== undefined ? moment(this.value).toDate() : new Date(); 98 98 this.picker = { 99 99 date: date, 100 100 open: false, ··· 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 } ··· 506 511 JoinAllOneController.$inject = ["$scope", "$uibModalInstance", 'JourneyService', 'uiCalendarConfig', 'journeyId']; 507 512 508 513 514 + /** 515 + * Controller for the modal showed when a journey has repetitions. 516 + */ 517 + class LeaveAllOneController { 518 + constructor($scope, $uibModalInstance, journeyId) { 519 + this.$scope = $scope; 520 + this.$uibModalInstance = $uibModalInstance; 521 + this.journeyId = journeyId; 522 + } 523 + 524 + $onInit() { 525 + this.$scope.cancel = ($event) => { 526 + this.$uibModalInstance.dismiss(false); 527 + }; 528 + this.$scope.one = ($event) => { 529 + this.$uibModalInstance.close("one"); 530 + }; 531 + this.$scope.all = ($event) => { 532 + this.$uibModalInstance.close("all"); 533 + }; 534 + } 535 + } 536 + LeaveAllOneController.$inject = ["$scope", "$uibModalInstance", 'journeyId']; 537 + 538 + 509 539 class RecurrenceCalendarController { 510 540 511 541 constructor($scope, JourneyService, uiCalendarConfig) { ··· 619 649 } 620 650 ConfirmRejectPassengerController.$inject = ["$scope", "$uibModalInstance"]; 621 651 652 + 653 + class ConfirmThrowPassengerController { 654 + constructor($scope, $uibModalInstance) { 655 + this.$scope = $scope; 656 + this.$uibModalInstance = $uibModalInstance; 657 + } 658 + 659 + $onInit() { 660 + this.$scope.continue = ($event) => { 661 + this.$uibModalInstance.close(true); 662 + }; 663 + this.$scope.cancel = ($event) => { 664 + this.$uibModalInstance.dismiss(false); 665 + }; 666 + } 667 + } 668 + ConfirmThrowPassengerController.$inject = ["$scope", "$uibModalInstance"]; 669 + 670 + 622 671 export {OriginDestinationSelectController, DatetimeController, TimeController, 623 672 DateController, CalendarController, CircleMapController, JoinAllOneController, 624 - RecurrenceCalendarController, ConfirmRejectPassengerController}; 673 + RecurrenceCalendarController, ConfirmRejectPassengerController, 674 + LeaveAllOneController, ConfirmThrowPassengerController};
+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/static/src/sass/variables.scss
··· 21 21 sm: 540px, 22 22 md: 720px, 23 23 lg: 950px, 24 - xl: 950px 24 + xl: 1000px 25 25 ) !default;
+6 -2
upvcarshare/templates/header.html
··· 27 27 <nav class="navbar navbar-full navbar-dark bg-upv-dark-grey"> 28 28 {% if request.user.is_authenticated %} 29 29 <ul class="nav navbar-nav pull-xs-left"> 30 + <li class="nav-item {% add_active_class "journeys:list" %}"> 31 + <a class="nav-link" href="{% url "journeys:list" %}"> 32 + {% trans "Mis viajes" %} 33 + </a> 34 + </li> 30 35 <li class="nav-item {% add_active_class "notifications:list" %}"> 31 36 <a class="nav-link" href="{% url "notifications:list" %}"> 32 37 {% trans "Notificaciones" %} ··· 46 51 <li class="nav-item {% add_active_class "journeys:create" %} dropdown"> 47 52 <a class="nav-link" href="{% url "journeys:create" %}">{% trans "Crear Viaje" %}</a> 48 53 </li> 49 - <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"> 50 55 <a class="nav-link dropdown-toggle" 51 56 data-toggle="dropdown" 52 57 href="#" ··· 58 63 <div class="dropdown-menu"> 59 64 <a class="dropdown-item" href="{% url "users:edit" %}">{% trans "Editar perfil" %}</a> 60 65 <div class="dropdown-divider"></div> 61 - <a class="dropdown-item" href="{% url "journeys:list" %}">{% trans "Mis viajes" %}</a> 62 66 <a class="dropdown-item" href="{% url "journeys:residences" %}">{% trans "Mis lugares" %}</a> 63 67 <a class="dropdown-item" href="{% url "journeys:transports" %}">{% trans "Mis transportes" %}</a> 64 68 <div class="dropdown-divider"></div>
+8 -1
upvcarshare/templates/journeys/blocks/details.passengers.html
··· 17 17 {% if request.user == journey.driver and request.user == journey.user %} 18 18 {% if passenger.status == CONFIRMED %} 19 19 <td colspan="2"> 20 - <form method="post" action="{% url "journeys:throw-out" passenger.pk %}"> 20 + <form method="post" action="{% url "journeys:throw-out" passenger.pk %}" {% if passenger.has_recurrence %}throw-passenger-form{% endif %}> 21 21 {% csrf_token %} 22 22 <input type="hidden" name="return_to" value="{{ request.path }}"> 23 23 <button type="submit" class="btn btn-danger">{% trans "Expulsar" %}</button> 24 + <script type="text/ng-template" id="throw-passenger.html"> 25 + <div class="modal-body text-xs-center"> 26 + <p class="lead">{% trans "El usuario se ha apuntado a más de una de las ocurrencias de este viaje" %}</p> 27 + <button class="btn btn-lg btn-danger" type="button" ng-click="cancel($event)">{% trans "Cancelar" %}</button> 28 + <button class="btn btn-lg btn-success" type="button" ng-click="continue($event)">{% trans "Expulsar para todos" %}</button> 29 + </div> 30 + </script> 24 31 </form> 25 32 </td> 26 33 {% else %}
+8 -6
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 %} ··· 48 48 {% endblock %} 49 49 </div> 50 50 </div> 51 - {% elif field.name == "transport" %} 51 + {% elif field.name == "transport" %} 52 52 <div class="row"> 53 53 <div class="col-sm-7"> 54 54 {{ field }} 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 + <r-rules field-id="id_recurrence" field-name="recurrence" override-departure="newDepartureValue"></r-rules> 64 65 {% elif field.name == "arrival" %} 65 - <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 + {% elif field.name == "recurrence" %} 66 68 {% else %} 67 69 {{ field }} 68 70 {% endif %} ··· 73 75 {% endif %} 74 76 {% endfor %} 75 77 <div class="form-group"> 76 - <div class="col-sm-2 pull-right"> 78 + <div class="offset-sm-4 col-sm-8"> 77 79 <button type="submit" class="btn btn-success">{% trans "Guardar" %}</button> 78 80 <a href="{% url "journeys:list" %}" class="btn btn-danger">{% trans "Volver" %}</a> 79 81 </div>
+16 -2
upvcarshare/templates/journeys/templatetags/join_leave_button.html
··· 26 26 {% endif %} 27 27 </form> 28 28 {% elif not journey_item.needs_driver and journey_item|is_passenger:request.user %} 29 - <form method="post" action="{% url "journeys:leave" journey_item.pk %}"> 29 + <form method="post" action="{% url "journeys:leave" journey_item.pk %}" {% if journey_item.has_recurrence %}leave-journey-form journey-id="{{ journey_item.pk }}"{% endif %}> 30 30 {% csrf_token %} 31 31 <input type="hidden" name="return_to" value="{{ request.path }}"> 32 - <button type="submit" class="btn btn-danger">{% trans "Abandonar" %}</button> 32 + {% if not journey_item.has_recurrence %} 33 + <input type="hidden" name="leave_from" value="one"> 34 + <button type="submit" class="btn btn-danger">{% trans "Abandonar" %}</button> 35 + {% else %} 36 + <script type="text/ng-template" id="leave-all-one.html"> 37 + <div class="modal-body text-xs-center"> 38 + <p class="lead">{% trans "Esté viaje tiene más de una ocurrencia, ¿quieres abandonar todos o sólo este?" %}</p> 39 + <button class="btn btn-lg btn-danger" type="button" ng-click="cancel($event)">{% trans "Cancelar" %}</button> 40 + <button class="btn btn-lg btn-success" type="button" ng-click="one($event)">{% trans "Abandonar sólo este" %}</button> 41 + <button class="btn btn-lg btn-success" type="button" ng-click="all($event)">{% trans "Abandonar todos" %}</button> 42 + </div> 43 + </script> 44 + <input type="hidden" name="leave_from" ng-value="leaveFromValue"> 45 + <button type="submit" class="btn btn-danger">{% trans "Abandonar" %}</button> 46 + {% endif %} 33 47 </form> 34 48 {% endif %}
+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>
+64
upvcarshare/templates/partials/journeys/rrules.html
··· 1 + {% load i18n %} 2 + <div class="row"> 3 + <div class="col-xs-12"> 4 + <input type="hidden" name="[[$ctrl.fieldName]]" id="[[$ctrl.fieldId]]" ng-value="$ctrl.rruleString"/> 5 + <label class="form-check-label"> 6 + <input type="checkbox" ng-model="$ctrl.isActivated" /> {% trans "Quiero realizar el viaje más de una vez" %} 7 + </label> 8 + </div> 9 + </div> 10 + <div class="row" ng-class="{'hidden-xs-up': !$ctrl.isActivated}" style="margin-top: 15px"> 11 + <div class="col-xs-12"> 12 + <label>{% trans "Se repite:" %}</label> 13 + <select ng-init="$ctrl.rrule.freq = $ctrl.freqOptions[2].value" 14 + ng-model="$ctrl.rrule.freq" 15 + ng-options="freqOption.value as freqOption.label for freqOption in $ctrl.freqOptions"> 16 + > 17 + </select> 18 + </div> 19 + <div class="col-xs-12"> 20 + <label>{% trans "Repetir cada:" %}</label> 21 + <select ng-model="$ctrl.rrule.interval" 22 + ng-options="intervalOption as intervalOption for intervalOption in $ctrl.intervalOptions"> 23 + > 24 + </select> 25 + [[$ctrl.intervalLabel]] 26 + </div> 27 + <div class="col-xs-12" ng-class="{'hidden-xs-up': $ctrl.hiddeByweekday()}"> 28 + <label>{% trans "Repetir el:" %}</label> 29 + <label class="form-check-label" ng-repeat="byweekdayOption in $ctrl.byweekdayOptions"> 30 + <input type="checkbox" 31 + ng-model="byweekdayOption.model" 32 + /> [[byweekdayOption.label]] 33 + </label> 34 + </div> 35 + <div class="col-xs-12"> 36 + <div class="row"> 37 + <div class="col-xs-1"> 38 + <label>{% trans "Finaliza:" %}</label> 39 + </div> 40 + <div class="col-xs-11"> 41 + <ul class="list-unstyled"> 42 + <li> 43 + <label> 44 + <input type="radio" name="ends" ng-model="$ctrl.ends" value="never"> {% trans "Nunca" %} 45 + </label> 46 + </li> 47 + <li> 48 + <label> 49 + <input type="radio" name="ends" ng-model="$ctrl.ends" value="count"> {% trans "Al cabo de" %} <input type="text" ng-model="$ctrl.rrule.count" class="form-control" style="width: 40px; display: inline-block"> {% trans "repeticiones" %} 50 + </label> 51 + </li> 52 + <li> 53 + <label> 54 + <input type="radio" name="ends" ng-model="$ctrl.ends" value="until"> {% trans "El" %} <input type="date" ng-model="$ctrl.rrule.until" class="form-control" style="width: 150px; display: inline-block"> 55 + </label> 56 + </li> 57 + </ul> 58 + </div> 59 + </div> 60 + </div> 61 + <div class="col-xs-12"> 62 + <p><strong>Resumen: [[$ctrl.rruleText]]</strong></p> 63 + </div> 64 + </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">
+1 -1
upvcarshare/users/tests/test_api.py
··· 42 42 self.assertEqual(response.status_code, status.HTTP_200_OK) 43 43 user = User.objects.get(pk=user.pk) 44 44 self.assertEquals( 45 - "SRID=4326;POINT (-0.3767699999989411 39.46913999999702)", 45 + "SRID=4326;POINT (-0.3767699999999994 39.4691399999998)", 46 46 six.text_type(user.get_default_position_wgs84()) 47 47 ) 48 48 self.assertEquals("foo", user.default_address)