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.

views and test for views

+1252 -61
+3
.babelrc
··· 1 + { 2 + "presets": ["es2015"] 3 + }
+3 -6
.gitignore
··· 27 27 docs/_build 28 28 29 29 # SQLite3 database files: 30 - upvcarshare/*.db 30 + *.db 31 31 32 32 # Pycharm 33 33 .idea ··· 55 55 # virtual environments 56 56 .env 57 57 58 - # Ignore oracle 59 - oracle-xe-11.2.0-1.0.x86_64.rpm 60 - 61 58 # Project specific 62 59 upvcarshare/public 63 - upvcarshare/static/css/bundle.css 64 - upvcarshare/static/js/bundle.js 60 + upvcarshare/static/dist/css 61 + upvcarshare/static/dist/js
+6
README.rst
··· 12 12 brew install spatialite-tools 13 13 brew install gdal 14 14 15 + 16 + Static files 17 + ------------ 18 + 19 + The default folder for Django's ``STATICFILES_DIRS`` value is ``/static/dist/``, therefore all 20 + static data have to be created by **gulp**.
+79 -13
gulpfile.js
··· 2 2 var gulp = require('gulp'); 3 3 var sass = require('gulp-sass'); 4 4 var rename = require("gulp-rename"); 5 + var uglify = require('gulp-uglify'); 6 + var beautify = require('gulp-beautify'); 7 + var gulpif = require('gulp-if'); 8 + var util = require('gulp-util'); 9 + var browserify = require('browserify'); 10 + var watchify = require('watchify'); 11 + var babelify = require('babelify'); 12 + var source = require('vinyl-source-stream'); 13 + var buffer = require('vinyl-buffer'); 14 + 5 15 var config = require('./package.json'); 16 + 6 17 7 18 // Helper for handle static paths 8 19 // ----------------------------------------------------------------------------- 9 20 var pathsConfig = function (appName) { 10 21 var app = appName || config.name; 11 22 return { 12 - app: app, 13 - templates: app + '/templates', 14 - css: app + '/static/css', 15 - sass: app + '/static/sass', 16 - fonts: app + '/static/fonts', 17 - images: app + '/static/images', 18 - js: app + '/static/js', 19 - manageScript: app + 'manage.py' 23 + app: app, 24 + templates: app + '/templates', 25 + dist: { 26 + css: app + '/static/dist/css', 27 + fonts: app + '/static/dist/fonts', 28 + images: app + '/static/dist/images', 29 + js: app + '/static/dist/js' 30 + }, 31 + src: { 32 + sass: app + '/static/src/sass', 33 + fonts: app + '/static/src/fonts', 34 + images: app + '/static/src/images', 35 + js: app + '/static/src/js' 36 + }, 37 + manageScript: app + 'manage.py' 20 38 } 21 39 }; 22 40 ··· 34 52 // Common 'run' code for each options 35 53 var run = function (options) { 36 54 options = sassOptions || options; 37 - gulp.src(pathsConfig().sass + '/project.scss') 55 + gulp.src(pathsConfig().src.sass + '/project.scss') 38 56 .pipe(sass(options).on('error', sass.logError)) 39 57 .pipe(rename('bundle.css')) 40 - .pipe(gulp.dest(pathsConfig().css)); 58 + .pipe(gulp.dest(pathsConfig().dist.css)); 41 59 }; 42 60 43 61 // Run for development with watch ··· 51 69 52 70 }; 53 71 72 + 73 + // App Task 74 + // ----------------------------------------------------------------------------- 75 + // Task to build a bundle.js file with all the JavaScript code of the app, 76 + // using browserify. 77 + var appTask = function (options) { 78 + 79 + // App bundle creator 80 + var appBundler = browserify({ 81 + entries: [options.src], 82 + transform: [babelify], 83 + debug: options.development, 84 + cache: {}, 85 + packageCache: {}, 86 + fullPaths: options.development 87 + }); 88 + 89 + // The bundle process 90 + var bundle = function () { 91 + return appBundler.bundle() 92 + .on('error', util.log) 93 + .pipe(source('app.js')) 94 + .pipe(buffer()) 95 + .pipe(gulpif(!options.development, uglify(), beautify())) 96 + .pipe(rename('bundle.js')) 97 + .pipe(gulp.dest(options.dist)) 98 + }; 99 + 100 + // Fire up watchify when developing 101 + if (options.development) { 102 + appBundler = watchify(appBundler); 103 + appBundler.on('update', bundle); 104 + } 105 + 106 + // Call to create bundle 107 + bundle(); 108 + }; 109 + 54 110 // Default Task 55 111 // ----------------------------------------------------------------------------- 56 112 // Starts our development workflow 57 113 gulp.task('default', function () { 58 114 cssTask({ 59 - development: true, 60 - watch: pathsConfig().sass + "/**/*.scss" 115 + watch: pathsConfig().src.sass + "/**/*.scss", 116 + development: true 117 + }); 118 + appTask({ 119 + src: pathsConfig().src.js + "/app.js", 120 + dist: pathsConfig().dist.js, 121 + development: true 61 122 }) 62 123 }); 63 124 ··· 66 127 gulp.task('deploy', function () { 67 128 cssTask({ 68 129 development: false 69 - }) 130 + }); 131 + appTask({ 132 + src: pathsConfig().src.js + "/app.js", 133 + dist: pathsConfig().dist.js, 134 + development: false 135 + }); 70 136 });
+12 -1
package.json
··· 6 6 "license": "MIT", 7 7 "dependencies": {}, 8 8 "devDependencies": { 9 + "babel-preset-es2015": "^6.6.0", 10 + "babel-preset-react": "^6.5.0", 11 + "babelify": "^7.3.0", 9 12 "bootstrap": "^4.0.0-alpha.2", 13 + "browserify": "^13.0.1", 10 14 "gulp": "^3.9.1", 15 + "gulp-beautify": "^2.0.0", 16 + "gulp-if": "^2.0.1", 11 17 "gulp-rename": "^1.2.2", 12 18 "gulp-sass": "^2.3.1", 19 + "gulp-uglify": "^1.5.3", 20 + "gulp-util": "^3.0.7", 21 + "jquery": "^2.1.4", 13 22 "node-sass": "^3.4.2", 14 - "yuglify": "^0.1.4" 23 + "vinyl-buffer": "^1.0.0", 24 + "vinyl-source-stream": "^1.1.0", 25 + "watchify": "^3.7.0" 15 26 } 16 27 }
+4 -3
requirements/base.txt
··· 1 - django==1.9.5 1 + django==1.9.6 2 2 django-environ==0.4.0 3 3 django-braces==1.8.1 4 - django-extensions==1.6.3 4 + django-floppyforms==1.6.2 5 + django-extensions==1.6.7 5 6 logutils==0.3.3 6 7 django-model-utils==2.5 7 8 djangorestframework==3.3.3 9 + djangorestframework-gis==0.10.1 8 10 sphinx==1.4.1 9 11 sphinx-rtd-theme==0.1.9 10 12 sphinxcontrib-httpdomain==1.4.0 11 - django-pipeline==1.6.8
+23
upvcarshare/config/router.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django.conf.urls import url, include 5 + from rest_framework.routers import SimpleRouter 6 + 7 + from journeys.api.v1.resources import TransportResource, ResidenceResource, CampusResource, JourneyResource, \ 8 + join_journey, leave_journey 9 + from users.api.v1.resources import me 10 + 11 + router = SimpleRouter() 12 + 13 + router.register(r'transports', viewset=TransportResource) 14 + router.register(r'residences', viewset=ResidenceResource) 15 + router.register(r'campus', viewset=CampusResource) 16 + router.register(r'journeys', viewset=JourneyResource) 17 + 18 + urlpatterns = [ 19 + url(r'^journeys/(?P<pk>[^/.]+)/join/$', join_journey, name='join-journey'), 20 + url(r'^journeys/(?P<pk>[^/.]+)/leave/$', leave_journey, name='leave-journey'), 21 + url(r'^users/me/$', me, kwargs={'pk': 'me'}), 22 + url(r'^', include(router.urls)), 23 + ]
+7 -3
upvcarshare/config/settings/base.py
··· 3 3 from sys import path 4 4 5 5 import environ 6 + from django.core.urlresolvers import reverse_lazy 6 7 7 8 env = environ.Env() 8 9 ··· 145 146 'django.contrib.humanize', 146 147 'django.contrib.admin', 147 148 'django.contrib.admindocs', 149 + 'django.contrib.gis', 148 150 ) 149 151 150 152 THIRD_PARTY_APPS = ( 151 153 'django_extensions', 154 + 'floppyforms', 152 155 'rest_framework', 156 + 'rest_framework_gis', 153 157 'rest_framework.authtoken', 154 - 'pipeline', 155 158 ) 156 159 157 160 PROJECT_APPS = ( 161 + 'pages', 158 162 'users', 159 163 'journeys', 160 164 ) ··· 179 183 180 184 # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS 181 185 STATICFILES_DIRS = ( 182 - str(APPS_DIR.path("static")), 186 + str(APPS_DIR.path("static/dist")), 183 187 ) 184 188 185 189 # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-STATICFILES_STORAGE ··· 210 214 # Select the correct user model 211 215 AUTH_USER_MODEL = 'users.User' 212 216 LOGIN_REDIRECT_URL = 'users:redirect' 213 - LOGIN_URL = 'account_login' 217 + LOGIN_URL = reverse_lazy('users:sign-in') 214 218 215 219 # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 216 220 AUTH_PASSWORD_VALIDATORS = [
+1 -1
upvcarshare/config/settings/local.py
··· 18 18 # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases 19 19 DATABASES = { 20 20 'default': env.db( 21 - 'DATABASE_URL', default='spatialite:///{}'.format(str(APPS_DIR.path('{}.db'.format(PROJECT_NAME.lower())))) 21 + 'DATABASE_URL', default='spatialite:///{}'.format(str(ROOT_DIR.path('{}.db'.format(PROJECT_NAME.lower())))) 22 22 ), 23 23 } 24 24 DATABASES['default']['ATOMIC_REQUESTS'] = True
-4
upvcarshare/config/settings/test.py
··· 15 15 "default": { 16 16 "ENGINE": "django.contrib.gis.db.backends.spatialite", 17 17 "NAME": ":memory:", 18 - "USER": "", 19 - "PASSWORD": "", 20 - "HOST": "", 21 - "PORT": "", 22 18 }, 23 19 } 24 20 DATABASES['default']['ATOMIC_REQUESTS'] = True
+31 -1
upvcarshare/config/urls.py
··· 1 1 # -*- coding: utf-8 -*- 2 - from django.conf.urls import url 2 + from django.conf import settings 3 + from django.conf.urls import url, include 3 4 from django.contrib import admin 5 + from django.views import defaults as default_views 6 + from django.views.generic import TemplateView 4 7 8 + from config.router import urlpatterns as api_urlpatterns 9 + 10 + # App URLs 5 11 urlpatterns = [ 12 + url(r"^$", TemplateView.as_view(template_name="pages/home.html"), name="home"), 13 + url(r"^", include("pages.urls", namespace="pages")), 14 + url(r"^users/", include("users.urls", namespace="users")), 15 + url(r"^journeys/", include("journeys.urls", namespace="journeys")), 16 + ] 17 + 18 + # Admin URLs 19 + urlpatterns += [ 6 20 url(r'^admin/', admin.site.urls), 7 21 ] 22 + 23 + # API URLs 24 + # Create a router and register our resources with it. 25 + urlpatterns += [ 26 + url(r'^api/v1/', include(api_urlpatterns, namespace="api_v1")), 27 + ] 28 + 29 + if settings.DEBUG: 30 + # This allows the error pages to be debugged during development, just visit 31 + # these url in browser to see how these error pages look like. 32 + urlpatterns += [ 33 + url(r'^400/$', default_views.bad_request, kwargs={'exception': Exception('Bad Request!')}), 34 + url(r'^403/$', default_views.permission_denied, kwargs={'exception': Exception('Permission Denied')}), 35 + url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception('Page not Found')}), 36 + url(r'^500/$', default_views.server_error), 37 + ]
+2 -1
upvcarshare/journeys/__init__.py
··· 11 11 # Uses projected coordinate system for Spain. 12 12 # See: https://epsg.io/2062 13 13 DEFAULT_PROJECTED_SRID = 2062 14 + DEFAULT_WGS84_SRID = 4326 14 15 # Distance in meters 15 - DEFAULT_CAMPUS_DISTANCE = 500 16 + DEFAULT_DISTANCE = 500
+102
upvcarshare/journeys/api/v1/resources.py
··· 1 1 # -*- coding: utf-8 -*- 2 2 from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from rest_framework import viewsets, status 5 + from rest_framework.generics import get_object_or_404 6 + from rest_framework.permissions import IsAuthenticated 7 + from rest_framework.response import Response 8 + 9 + from journeys.api.v1.serializers import TransportSerializer, ResidenceSerializer, CampusSerializer, JourneySerializer 10 + from journeys.models import Transport, Residence, Campus, Journey 11 + 12 + 13 + class TransportResource(viewsets.ModelViewSet): 14 + """Resource to access all transports that a user has.""" 15 + 16 + queryset = Transport.objects.all() 17 + serializer_class = TransportSerializer 18 + permission_classes = [ 19 + IsAuthenticated, 20 + ] 21 + 22 + def get_queryset(self): 23 + queryset = super(TransportResource, self).get_queryset() 24 + queryset = queryset.filter(user=self.request.user) 25 + return queryset 26 + 27 + def perform_create(self, serializer): 28 + """On create, set the user who makes the request the owner.s""" 29 + serializer.save(user=self.request.user) 30 + 31 + 32 + class ResidenceResource(viewsets.ModelViewSet): 33 + """Resource to access to residences. A user can create residences.""" 34 + 35 + queryset = Residence.objects.all() 36 + serializer_class = ResidenceSerializer 37 + permission_classes = [ 38 + IsAuthenticated, 39 + ] 40 + 41 + def perform_create(self, serializer): 42 + """On create, set the user who makes the request the owner.s""" 43 + serializer.save(user=self.request.user) 44 + 45 + 46 + class CampusResource(viewsets.ModelViewSet): 47 + """Resource to access all campus created on database. A user can't create a Campus.""" 48 + 49 + queryset = Campus.objects.all() 50 + serializer_class = CampusSerializer 51 + permission_classes = [ 52 + IsAuthenticated, 53 + ] 54 + 55 + 56 + class JourneyResource(viewsets.ModelViewSet): 57 + """Resource to get, create or update journeys.""" 58 + 59 + queryset = Journey.objects.all() 60 + serializer_class = JourneySerializer 61 + permission_classes = [ 62 + IsAuthenticated, 63 + ] 64 + 65 + 66 + class JoinJourneyResource(viewsets.ViewSet): 67 + """Resource to allow users to join a journey. 68 + 69 + Example: 70 + POST /api/v1/journeys/1/join/ 71 + """ 72 + permission_classes = [ 73 + IsAuthenticated, 74 + ] 75 + 76 + @staticmethod 77 + def join(request, **kwargs): 78 + pk = kwargs.get('pk', 0) 79 + journey = get_object_or_404(Journey, pk=pk) 80 + journey.join_passenger(request.user) 81 + return Response(status=status.HTTP_201_CREATED) 82 + 83 + join_journey = JoinJourneyResource.as_view({"post": "join"}) 84 + 85 + 86 + class LeaveJourneyResource(viewsets.ViewSet): 87 + """Resource to allow users to leave a journey. 88 + 89 + Example: 90 + POST /api/v1/journeys/1/leave/ 91 + """ 92 + 93 + permission_classes = [ 94 + IsAuthenticated, 95 + ] 96 + 97 + @staticmethod 98 + def leave(request, **kwargs): 99 + pk = kwargs.get('pk', 0) 100 + journey = get_object_or_404(Journey, pk=pk) 101 + journey.leave_passenger(request.user) 102 + return Response(status=status.HTTP_201_CREATED) 103 + 104 + leave_journey = LeaveJourneyResource.as_view({"post": "leave"})
+61
upvcarshare/journeys/api/v1/serializers.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from rest_framework import serializers 5 + from rest_framework_gis.fields import GeometryField 6 + 7 + from journeys import DEFAULT_WGS84_SRID 8 + from journeys.helpers import make_point_projected 9 + from journeys.models import Transport, Place, Residence, Campus, Journey 10 + from users.api.v1.serializers import UserSerializer 11 + 12 + 13 + class TransportSerializer(serializers.ModelSerializer): 14 + 15 + class Meta: 16 + model = Transport 17 + exclude = ["user"] 18 + 19 + 20 + class PlaceSerializer(serializers.ModelSerializer): 21 + 22 + position = GeometryField(source="get_position_wgs84") 23 + 24 + class Meta: 25 + model = Place 26 + fields = ["name", "distance", "position"] 27 + 28 + def validate(self, attrs): 29 + if 'get_position_wgs84' in attrs: 30 + position = attrs.pop('get_position_wgs84') 31 + position.srid = DEFAULT_WGS84_SRID 32 + attrs['position'] = make_point_projected(position) 33 + return attrs 34 + 35 + 36 + class ResidenceSerializer(PlaceSerializer): 37 + 38 + class Meta(PlaceSerializer.Meta): 39 + model = Residence 40 + fields = ["name", "distance", "position", "address"] 41 + 42 + 43 + class CampusSerializer(PlaceSerializer): 44 + 45 + class Meta(PlaceSerializer.Meta): 46 + model = Campus 47 + 48 + 49 + class JourneySerializer(serializers.ModelSerializer): 50 + 51 + user = UserSerializer() 52 + residence = ResidenceSerializer() 53 + campus = CampusSerializer() 54 + driver = UserSerializer() 55 + 56 + current_free_places = serializers.IntegerField() 57 + 58 + class Meta: 59 + model = Journey 60 + fields = ["user", "driver", "residence", "campus", "kind", "free_places", "departure", "disabled", 61 + "current_free_places"]
+49
upvcarshare/journeys/forms.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django import forms 5 + 6 + from journeys.models import Residence, Journey 7 + from users.models import User 8 + 9 + 10 + class ResidenceForm(forms.ModelForm): 11 + 12 + class Meta: 13 + model = Residence 14 + fields = ["name", "address", "position", "distance"] 15 + 16 + def save(self, commit=True, **kwargs): 17 + """When save a residence form, you have to provide an user.""" 18 + assert "user" in kwargs 19 + assert isinstance(kwargs["user"], User) 20 + residence = super(ResidenceForm, self).save(commit=False) 21 + residence.user = kwargs.get("user") 22 + if commit: 23 + residence.save() 24 + return residence 25 + 26 + 27 + class JourneyForm(forms.ModelForm): 28 + 29 + class Meta: 30 + model = Journey 31 + fields = ["residence", "campus", "kind", "free_places", "departure", "disabled"] 32 + 33 + def __init__(self, *args, **kwargs): 34 + self.user = kwargs.pop("user") 35 + super(JourneyForm, self).__init__(*args, **kwargs) 36 + if self.user: 37 + self.fields['residence'].queryset = Residence.objects.filter(user=self.user) 38 + 39 + def save(self, commit=True, **kwargs): 40 + """When save a journey form, you have to provide an user.""" 41 + user = self.user 42 + if "user" in kwargs: 43 + assert isinstance(kwargs["user"], User) 44 + user = kwargs.get("user") 45 + journey = super(JourneyForm, self).save(commit=False) 46 + journey.user = user 47 + if commit: 48 + journey.save() 49 + return journey
+22
upvcarshare/journeys/helpers.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django.contrib.gis.gdal import SpatialReference, CoordTransform 5 + 6 + from journeys import DEFAULT_PROJECTED_SRID, DEFAULT_WGS84_SRID 7 + 8 + 9 + def make_point_wgs84(point): 10 + destination_coord = SpatialReference(DEFAULT_PROJECTED_SRID) 11 + origin_coord = SpatialReference(DEFAULT_WGS84_SRID) 12 + trans = CoordTransform(origin_coord, destination_coord) 13 + point.transform(trans) 14 + return point 15 + 16 + 17 + def make_point_projected(point): 18 + origin_coord = SpatialReference(DEFAULT_WGS84_SRID) 19 + destination_coord = SpatialReference(DEFAULT_PROJECTED_SRID) 20 + trans = CoordTransform(origin_coord, destination_coord) 21 + point.transform(trans) 22 + return point
+9 -3
upvcarshare/journeys/managers.py
··· 7 7 from django.db.models import Count, F, Q 8 8 from django.utils import timezone 9 9 10 - from journeys import GOING, RETURN 10 + from journeys import GOING, RETURN, DEFAULT_DISTANCE 11 11 12 12 13 13 def recommended_condition(journey): 14 14 """Creates a condition to mark a journey as recommended, based on kind and a 15 15 needed journey. 16 - :param kind: 17 16 :param journey: 18 17 """ 19 18 key = "residence{}" if journey.kind == GOING else "campus{}" ··· 28 27 class ResidenceManager(models.GeoManager): 29 28 30 29 def smart_create(self, user): 31 - """Smart create using data from user.""" 30 + """Smart create using data from user. 31 + :param user: 32 + :param distance: 33 + """ 32 34 return self.create( 35 + user=user, 33 36 address=user.default_address, 34 37 position=user.default_position, 38 + distance=user.default_distance 35 39 ) 36 40 37 41 ··· 108 112 # Gets journeys needed by the user... 109 113 needed_journeys = self.needed(user=user, kind=kind) 110 114 conditions = [Q(**recommended_condition(journey=journey)) for journey in needed_journeys] 115 + if not conditions: 116 + return self.none() 111 117 return self.available(kind=kind).exclude(user=user).filter(reduce(lambda x, y: x | y, conditions))
+16 -4
upvcarshare/journeys/models.py
··· 3 3 4 4 from django.conf import settings 5 5 from django.contrib.gis.db import models 6 + from django.contrib.gis.gdal import SpatialReference, CoordTransform 6 7 from django.contrib.gis.measure import D 7 8 from django_extensions.db.models import TimeStampedModel 8 9 from django.utils.translation import ugettext_lazy as _ 9 10 10 11 from core.models import GisTimeStampedModel 11 - from journeys import JOURNEY_KINDS, GOING, RETURN, DEFAULT_CAMPUS_DISTANCE, DEFAULT_PROJECTED_SRID 12 + from journeys import JOURNEY_KINDS, GOING, RETURN, DEFAULT_DISTANCE, DEFAULT_PROJECTED_SRID, DEFAULT_WGS84_SRID 12 13 from journeys.exceptions import NoFreePlaces, NotAPassenger, AlreadyAPassenger 14 + from journeys.helpers import make_point_wgs84 13 15 from journeys.managers import JourneyManager, ResidenceManager 14 16 15 17 ··· 26 28 class Meta: 27 29 abstract = True 28 30 29 - def position_wgs84(self): 31 + def get_position_wgs84(self): 30 32 """Transforms position to WGS-84 system.""" 31 - return self.position.transform(4326) 33 + destination_coord = SpatialReference(DEFAULT_WGS84_SRID) 34 + origin_coord = SpatialReference(DEFAULT_PROJECTED_SRID) 35 + trans = CoordTransform(origin_coord, destination_coord) 36 + position = self.position 37 + position.transform(trans) 38 + return position 39 + 40 + def set_position_wgs84(self, position): 41 + """Transforms an input to projected coordinates.""" 42 + self.position = make_point_wgs84(position) 43 + return self.position 32 44 33 45 def nearby(self): 34 46 """Abstract method to search nearby journeys.""" ··· 58 70 59 71 def save(self, **kwargs): 60 72 if not self.distance: 61 - self.distance = DEFAULT_CAMPUS_DISTANCE 73 + self.distance = DEFAULT_DISTANCE 62 74 super(Campus, self).save(**kwargs) 63 75 64 76
+117
upvcarshare/journeys/tests/test_api.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + import json 5 + import random 6 + 7 + from rest_framework import status 8 + from rest_framework.test import APITestCase 9 + 10 + from journeys import GOING, RETURN 11 + from journeys.models import Transport 12 + from journeys.tests.factories import TransportFactory, ResidenceFactory, CampusFactory, JourneyFactory 13 + from users.tests.factories import UserFactory 14 + 15 + 16 + class TransportsAPITests(APITestCase): 17 + 18 + def setUp(self): 19 + self.user = UserFactory() 20 + self.transports = [TransportFactory(user=self.user) for _ in range(5)] 21 + self.other_transport = TransportFactory(user=UserFactory()) 22 + 23 + def test_get_transports(self): 24 + url = "/api/v1/transports/" 25 + self.client.force_authenticate(user=self.user) 26 + response = self.client.get(url) 27 + self.assertEqual(response.status_code, status.HTTP_200_OK) 28 + response_data = json.loads(response.content.decode('utf-8')) 29 + self.assertEquals(len(self.transports), len(response_data['results'])) 30 + 31 + def test_update_transport(self): 32 + url = "/api/v1/transports/{}/".format(self.transports[0].pk) 33 + data = { 34 + "default_places": 2 35 + } 36 + self.client.force_authenticate(user=self.user) 37 + response = self.client.patch(url, data=data) 38 + self.assertEqual(response.status_code, status.HTTP_200_OK) 39 + transport = Transport.objects.get(pk=self.transports[0].pk) 40 + self.assertEquals(2, transport.default_places) 41 + 42 + 43 + class ResidenceAPITest(APITestCase): 44 + 45 + def test_get_residences(self): 46 + user = UserFactory() 47 + residences = [ResidenceFactory(user=user) for _ in range(5)] 48 + url = "/api/v1/residences/" 49 + self.client.force_authenticate(user=user) 50 + response = self.client.get(url) 51 + self.assertEqual(response.status_code, status.HTTP_200_OK) 52 + response_data = json.loads(response.content.decode('utf-8')) 53 + self.assertEquals(len(residences), len(response_data['results'])) 54 + 55 + 56 + class CampusAPITest(APITestCase): 57 + 58 + def test_get_residences(self): 59 + user = UserFactory() 60 + campus = [CampusFactory() for _ in range(5)] 61 + url = "/api/v1/campus/" 62 + self.client.force_authenticate(user=user) 63 + response = self.client.get(url) 64 + self.assertEqual(response.status_code, status.HTTP_200_OK) 65 + response_data = json.loads(response.content.decode('utf-8')) 66 + self.assertEquals(len(campus), len(response_data['results'])) 67 + 68 + 69 + class JourneyAPITest(APITestCase): 70 + 71 + @staticmethod 72 + def _make_journey(kind=GOING): 73 + user = UserFactory() 74 + origin = ResidenceFactory(user=user) 75 + destination = CampusFactory() 76 + return JourneyFactory(user=user, residence=origin, campus=destination, kind=kind) 77 + 78 + def test_get_journeys(self): 79 + user = UserFactory() 80 + journeys = [self._make_journey(random.choice([GOING, RETURN])) for _ in range(5)] 81 + url = "/api/v1/journeys/" 82 + self.client.force_authenticate(user=user) 83 + response = self.client.get(url) 84 + self.assertEqual(response.status_code, status.HTTP_200_OK) 85 + response_data = json.loads(response.content.decode('utf-8')) 86 + self.assertEquals(len(journeys), len(response_data['results'])) 87 + 88 + def test_get_journey_details(self): 89 + user = UserFactory() 90 + journey = self._make_journey(GOING) 91 + url = "/api/v1/journeys/{}/".format(journey.pk) 92 + self.client.force_authenticate(user=user) 93 + response = self.client.get(url) 94 + self.assertEqual(response.status_code, status.HTTP_200_OK) 95 + response_data = json.loads(response.content.decode('utf-8')) 96 + self.assertIsNotNone(response_data["residence"]) 97 + self.assertIsNotNone(response_data["campus"]) 98 + 99 + def test_join_journey(self): 100 + user = UserFactory() 101 + journey = self._make_journey(GOING) 102 + url = "/api/v1/journeys/{}/join/".format(journey.pk) 103 + self.client.force_authenticate(user=user) 104 + response = self.client.post(url) 105 + self.assertEqual(response.status_code, status.HTTP_201_CREATED) 106 + self.assertEquals(1, journey.count_passengers()) 107 + 108 + def test_leave_journey(self): 109 + user = UserFactory() 110 + journey = self._make_journey(GOING) 111 + journey.join_passenger(user) 112 + self.assertEquals(1, journey.count_passengers()) 113 + url = "/api/v1/journeys/{}/leave/".format(journey.pk) 114 + self.client.force_authenticate(user=user) 115 + response = self.client.post(url) 116 + self.assertEqual(response.status_code, status.HTTP_201_CREATED) 117 + self.assertEquals(0, journey.count_passengers())
+12 -1
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 13 + from journeys.models import Journey, Passenger, Residence 14 14 from journeys.tests.factories import ResidenceFactory, CampusFactory, TransportFactory, JourneyFactory 15 15 from users.tests.factories import UserFactory 16 16 ··· 226 226 user=user4, 227 227 kind=GOING 228 228 ).count(), 2) 229 + 230 + 231 + class ResidenceTest(TestCase): 232 + 233 + def test_smart_create(self): 234 + user = UserFactory( 235 + default_address="bar", 236 + default_position=Point(882532.74, 545437.43, srid=DEFAULT_PROJECTED_SRID) 237 + ) 238 + residence = Residence.objects.smart_create(user) 239 + self.assertIsInstance(residence, Residence)
+130
upvcarshare/journeys/tests/test_views.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + import datetime 5 + 6 + from django.utils import timezone 7 + from test_plus import TestCase 8 + 9 + from journeys import GOING, RETURN 10 + from journeys.models import Journey, Residence 11 + from journeys.tests.factories import JourneyFactory, ResidenceFactory, CampusFactory 12 + from users.tests.factories import UserFactory 13 + 14 + 15 + class JourneyViewTests(TestCase): 16 + user_factory = UserFactory 17 + 18 + def setUp(self): 19 + self.user = self.make_user(username="foo") 20 + 21 + def test_get_create_journey(self): 22 + url_name = "journeys:create" 23 + self.assertLoginRequired(url_name) 24 + with self.login(self.user): 25 + response = self.get(url_name) 26 + self.response_200(response=response) 27 + 28 + def test_post_create_journey(self): 29 + self.assertLoginRequired("journeys:create") 30 + with self.login(self.user): 31 + data = { 32 + "residence": ResidenceFactory(user=self.user).pk, 33 + "campus": CampusFactory().pk, 34 + "kind": GOING, 35 + "free_places": 4, 36 + "departure": (timezone.now() + datetime.timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S") 37 + } 38 + response = self.post(url_name="journeys:create", data=data) 39 + self.response_200(response) 40 + self.assertEquals(1, Journey.objects.count()) 41 + 42 + def test_get_edit_journey(self): 43 + journey = JourneyFactory(user=self.user) 44 + url_name = "journeys:edit" 45 + self.assertLoginRequired(url_name, pk=journey.pk) 46 + with self.login(self.user): 47 + response = self.get(url_name, pk=journey.pk) 48 + self.response_200(response=response) 49 + 50 + def test_post_edit_journey(self): 51 + journey = JourneyFactory(user=self.user, kind=GOING) 52 + url_name = "journeys:edit" 53 + self.assertLoginRequired(url_name, pk=journey.pk) 54 + with self.login(self.user): 55 + data = { 56 + "residence": ResidenceFactory(user=self.user).pk, 57 + "campus": CampusFactory().pk, 58 + "kind": RETURN, 59 + "free_places": 4, 60 + "departure": (timezone.now() + datetime.timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S") 61 + } 62 + response = self.post(url_name=url_name, pk=journey.pk, data=data) 63 + self.response_200(response=response) 64 + journey = Journey.objects.get(pk=journey.pk) 65 + self.assertEquals(RETURN, journey.kind) 66 + 67 + def test_get_recommended_journey(self): 68 + url_name = "journeys:recommended" 69 + self.assertLoginRequired(url_name) 70 + with self.login(self.user): 71 + response = self.get(url_name) 72 + self.response_200(response=response) 73 + 74 + def test_get_user_list_journey(self): 75 + url_name = "journeys:user-list" 76 + self.assertLoginRequired(url_name) 77 + with self.login(self.user): 78 + response = self.get(url_name) 79 + self.response_200(response=response) 80 + 81 + 82 + class ResidenceViewTests(TestCase): 83 + user_factory = UserFactory 84 + 85 + def setUp(self): 86 + self.user = self.make_user(username="foo") 87 + 88 + def test_get_create_residence(self): 89 + url_name = "journeys:create-residence" 90 + self.assertLoginRequired(url_name) 91 + with self.login(self.user): 92 + response = self.get(url_name) 93 + self.response_200(response=response) 94 + 95 + def test_post_create_residence(self): 96 + self.assertLoginRequired("journeys:create-residence") 97 + with self.login(self.user): 98 + data = { 99 + "name": "Home", 100 + "address": "foo", 101 + "position": "POINT (-0.3819 39.4625)", 102 + "distance": 500, 103 + } 104 + response = self.post(url_name="journeys:create-residence", data=data) 105 + self.response_200(response) 106 + self.assertEquals(1, Residence.objects.count()) 107 + 108 + def test_get_edit_residence(self): 109 + residence = ResidenceFactory(user=self.user) 110 + url_name = "journeys:edit-residence" 111 + self.assertLoginRequired(url_name, pk=residence.pk) 112 + with self.login(self.user): 113 + response = self.get(url_name, pk=residence.pk) 114 + self.response_200(response=response) 115 + 116 + def test_post_edit_residence(self): 117 + residence = ResidenceFactory(user=self.user) 118 + url_name = "journeys:edit-residence" 119 + self.assertLoginRequired(url_name, pk=residence.pk) 120 + with self.login(self.user): 121 + data = { 122 + "name": "Home", 123 + "address": "bar", 124 + "position": "POINT (-0.3819 39.4625)", 125 + "distance": 500 126 + } 127 + response = self.post(url_name=url_name, pk=residence.pk, data=data) 128 + self.response_200(response=response) 129 + residence = Residence.objects.get(pk=residence.pk) 130 + self.assertEquals(data["address"], residence.address)
+16
upvcarshare/journeys/urls.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django.conf.urls import url 5 + 6 + from journeys.views import CreateJourneyView, CreateResidenceView, EditResidenceView, EditJourneyView, \ 7 + RecommendedJourneyView, CurrentUserJourneyView 8 + 9 + urlpatterns = [ 10 + url(r"recommended/$", RecommendedJourneyView.as_view(), name="recommended"), 11 + url(r"user-list/$", CurrentUserJourneyView.as_view(), name="user-list"), 12 + url(r"residences/create/$", CreateResidenceView.as_view(), name="create-residence"), 13 + url(r"residences/(?P<pk>\d+)/edit/$", EditResidenceView.as_view(), name="edit-residence"), 14 + url(r"create/$", CreateJourneyView.as_view(), name="create"), 15 + url(r"(?P<pk>\d+)/edit/$", EditJourneyView.as_view(), name="edit"), 16 + ]
+130
upvcarshare/journeys/views.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from braces.views import LoginRequiredMixin 5 + from django.shortcuts import render, get_object_or_404 6 + from django.views.generic import View 7 + 8 + from journeys.forms import JourneyForm, ResidenceForm 9 + from journeys.models import Journey, Residence 10 + 11 + 12 + class CreateResidenceView(LoginRequiredMixin, View): 13 + """View to show journey creation form and to handle its creation.""" 14 + 15 + template_name = "residences/create.html" 16 + 17 + def get(self, request): 18 + form = ResidenceForm() 19 + data = { 20 + "form": form 21 + } 22 + return render(request, self.template_name, data) 23 + 24 + def post(self, request): 25 + form = ResidenceForm(request.POST) 26 + data = { 27 + "form": form 28 + } 29 + if form.is_valid(): 30 + journey = form.save(user=request.user) 31 + data["journey"] = journey 32 + return render(request, self.template_name, data) 33 + 34 + 35 + class EditResidenceView(LoginRequiredMixin, View): 36 + """View to edit residences.""" 37 + 38 + template_name = "residences/edit.html" 39 + 40 + def get(self, request, pk): 41 + residence = get_object_or_404(Residence, pk=pk, user=request.user) 42 + form = ResidenceForm(instance=residence) 43 + data = { 44 + "form": form, 45 + "residence": residence, 46 + } 47 + return render(request, self.template_name, data) 48 + 49 + def post(self, request, pk): 50 + residence = get_object_or_404(Residence, pk=pk, user=request.user) 51 + form = ResidenceForm(request.POST, instance=residence) 52 + data = { 53 + "form": form, 54 + "residence": residence, 55 + } 56 + if form.is_valid(): 57 + form.save(user=request.user) 58 + return render(request, self.template_name, data) 59 + 60 + 61 + class CreateJourneyView(LoginRequiredMixin, View): 62 + """View to show journey creation form and to handle its creation.""" 63 + 64 + template_name = "journeys/create.html" 65 + 66 + def get(self, request): 67 + form = JourneyForm(user=request.user) 68 + data = { 69 + "form": form 70 + } 71 + return render(request, self.template_name, data) 72 + 73 + def post(self, request): 74 + form = JourneyForm(request.POST, user=request.user) 75 + data = { 76 + "form": form 77 + } 78 + if form.is_valid(): 79 + journey = form.save() 80 + data["journey"] = journey 81 + return render(request, self.template_name, data) 82 + 83 + 84 + class EditJourneyView(LoginRequiredMixin, View): 85 + """View to edit journeys.""" 86 + 87 + template_name = "journeys/edit.html" 88 + 89 + def get(self, request, pk): 90 + journey = get_object_or_404(Journey, pk=pk, user=request.user) 91 + form = JourneyForm(instance=journey, user=request.user) 92 + data = { 93 + "form": form, 94 + "journey": journey, 95 + } 96 + return render(request, self.template_name, data) 97 + 98 + def post(self, request, pk): 99 + journey = get_object_or_404(Journey, pk=pk, user=request.user) 100 + form = JourneyForm(request.POST, instance=journey, user=request.user) 101 + data = { 102 + "form": form, 103 + "journey": journey, 104 + } 105 + if form.is_valid(): 106 + form.save() 107 + return render(request, self.template_name, data) 108 + 109 + 110 + class RecommendedJourneyView(LoginRequiredMixin, View): 111 + """View to show to the user the list of recommended journeys according to his needs.""" 112 + template_name = "journeys/recommended.html" 113 + 114 + def get(self, request): 115 + kind_filter = request.GET.get("kind") 116 + data = { 117 + "journeys": Journey.objects.recommended(user=request.user, kind=kind_filter) 118 + } 119 + return render(request, self.template_name, data) 120 + 121 + 122 + class CurrentUserJourneyView(LoginRequiredMixin, View): 123 + """View to show to the user the list of his created journeys.""" 124 + template_name = "journeys/user_list.html" 125 + 126 + def get(self, request): 127 + data = { 128 + "journeys": Journey.objects.filter(user=request.user) 129 + } 130 + return render(request, self.template_name, data)
+2
upvcarshare/pages/__init__.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import
+5
upvcarshare/pages/urls.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + urlpatterns = [ 5 + ]
upvcarshare/static/dist/.gitkeep

This is a binary file and will not be displayed.

-13
upvcarshare/static/sass/project.scss upvcarshare/static/src/sass/patches.scss
··· 1 - // Import own SCSS files 2 - // ----------------------------------------------------------------------------- 3 - 4 - @import "variables"; 5 - 6 - // Import Bootstrap 7 - // ----------------------------------------------------------------------------- 8 - 9 - @import "bootstrap/scss/bootstrap"; 10 - 11 - // Project specific 12 - // ----------------------------------------------------------------------------- 13 - 14 1 // Alert colors 15 2 16 3 $white: #fff;
upvcarshare/static/sass/variables.scss upvcarshare/static/src/sass/variables.scss
+6
upvcarshare/static/src/js/app.js
··· 1 + // Import jquery and bootstrap to be accessible for all project 2 + import $ from 'jquery'; 3 + import jQuery from 'jquery'; 4 + import bootstrap from 'bootstrap'; 5 + window.$ = $; 6 + window.jQuery = jQuery;
upvcarshare/static/src/sass/guide.scss

This is a binary file and will not be displayed.

+13
upvcarshare/static/src/sass/project.scss
··· 1 + // Import own SCSS files that override Bootstrap 2 + // ----------------------------------------------------------------------------- 3 + @import "variables"; 4 + 5 + 6 + // Import Bootstrap 7 + // ----------------------------------------------------------------------------- 8 + @import "bootstrap/scss/bootstrap"; 9 + 10 + // Project specific 11 + // ----------------------------------------------------------------------------- 12 + @import "guide"; 13 + @import "patches";
+9 -5
upvcarshare/templates/base.html
··· 1 1 {% load static %} 2 + {% load i18n %} 2 3 <!DOCTYPE html> 3 4 <html lang="en"> 4 5 <head> ··· 6 7 <meta charset="utf-8"> 7 8 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 8 9 <meta http-equiv="x-ua-compatible" content="ie=edge"> 9 - 10 - <!-- Bootstrap CSS --> 10 + <!-- Bundle CSS --> 11 11 <link rel="stylesheet" href="{% static "css/bundle.css" %}"> 12 12 </head> 13 13 <body> 14 + {% block pre_body %}{% endblock pre_body %} 14 15 {% block body %} 16 + <div class="container"> 17 + {% block container %} 18 + {% endblock container %} 19 + </div> 15 20 {% endblock body %} 16 - <!-- jQuery first, then Bootstrap JS. --> 17 - <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> 18 - <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.2/js/bootstrap.min.js" integrity="sha384-vZ2WRJMwsjRMW/8U7i6PWi6AlO1L79snBrmgiDpgIWJ82z8eA5lenwvxbMV1PAh7" crossorigin="anonymous"></script> 21 + {% block post_body %}{% endblock post_body %} 22 + <!-- Bundle JS --> 19 23 <script src="{% static "js/bundle.js" %}"></script> 20 24 </body> 21 25 </html>
+1
upvcarshare/templates/journeys/create.html
··· 1 + {% extends "base.html" %}
+1
upvcarshare/templates/journeys/edit.html
··· 1 + {% extends "base.html" %}
+1
upvcarshare/templates/journeys/recommended.html
··· 1 + {% extends "base.html" %}
+1
upvcarshare/templates/journeys/user_list.html
··· 1 + {% extends "base.html" %}
+1
upvcarshare/templates/pages/home.html
··· 1 + {% extends "base.html" %}
+1
upvcarshare/templates/residences/create.html
··· 1 + {% extends "base.html" %}
+1
upvcarshare/templates/residences/edit.html
··· 1 + {% extends "base.html" %}
+1
upvcarshare/templates/users/edit.html
··· 1 + {% extends "base.html" %}
+1
upvcarshare/templates/users/sign_in.html
··· 1 + {% extends "base.html" %}
+22
upvcarshare/users/api/v1/resources.py
··· 1 1 # -*- coding: utf-8 -*- 2 2 from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from rest_framework import viewsets 5 + from rest_framework.permissions import IsAuthenticated 6 + 7 + from users.api.v1.serializers import UserSerializer 8 + from users.models import User 9 + 10 + 11 + class UserResource(viewsets.ModelViewSet): 12 + 13 + queryset = User.objects.all() 14 + serializer_class = UserSerializer 15 + permission_classes = [ 16 + IsAuthenticated, 17 + ] 18 + 19 + def get_object(self): 20 + if self.kwargs.get('pk', None) == 'me': 21 + self.kwargs['pk'] = self.request.user.pk 22 + return super(UserResource, self).get_object() 23 + 24 + me = UserResource.as_view({'get': 'retrieve', 'patch': 'partial_update'})
+50
upvcarshare/users/api/v1/serializers.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django.contrib.auth.hashers import make_password 5 + from rest_framework import serializers 6 + from rest_framework_gis.fields import GeometryField 7 + 8 + from journeys import DEFAULT_WGS84_SRID 9 + from journeys.helpers import make_point_projected 10 + from users.models import User 11 + 12 + 13 + class UserSerializer(serializers.ModelSerializer): 14 + 15 + default_position = GeometryField(source="get_default_position_wgs84") 16 + 17 + class Meta: 18 + model = User 19 + fields = [ 20 + "id", "username", "email", "first_name", "last_name", "password", "default_address", 21 + "default_position" 22 + ] 23 + extra_kwargs = {'password': {'write_only': True}} 24 + 25 + def validate_password(self, value): 26 + value = make_password(value) 27 + return value 28 + 29 + def validate(self, attrs): 30 + if 'get_default_position_wgs84' in attrs: 31 + position = attrs.pop('get_default_position_wgs84') 32 + position.srid = DEFAULT_WGS84_SRID 33 + attrs['default_position'] = make_point_projected(position) 34 + return attrs 35 + 36 + def create(self, validated_data): 37 + user = User.objects.create( 38 + username=validated_data['username'], 39 + first_name=validated_data.get('first_name'), 40 + last_name=validated_data.get('last_name'), 41 + ) 42 + user.set_password(validated_data['password']) 43 + user.save() 44 + return user 45 + 46 + def update(self, instance, validated_data): 47 + if 'get_default_position_wgs84' in validated_data: 48 + position = validated_data.pop('get_default_position_wgs84') 49 + validated_data['default_position'] = position 50 + return super(UserSerializer, self).update(instance, validated_data)
+52
upvcarshare/users/forms.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django import forms 5 + from django.utils.translation import ugettext_lazy as _ 6 + 7 + from users.models import User 8 + 9 + 10 + class SignInForm(forms.Form): 11 + """Form for handle in a user can log in.""" 12 + username = forms.fields.CharField( 13 + widget=forms.TextInput(attrs={ 14 + "class": "form-control", 15 + "placeholder": _("Username"), 16 + "autocapitalize": "off", 17 + "autocorrect": "off", 18 + "autofocus": "autofocus", 19 + }), 20 + error_messages={'required': _('The username is required')} 21 + ) 22 + password = forms.fields.CharField( 23 + widget=forms.PasswordInput(attrs={ 24 + "class": "form-control", 25 + "placeholder": _("Password") 26 + }), 27 + error_messages={'required': _('The password is required')} 28 + ) 29 + 30 + def clean_username(self): 31 + username = self.cleaned_data.get('username') 32 + if not User.objects.filter(username=username).exists(): 33 + raise forms.ValidationError(_("Username not found")) 34 + return username 35 + 36 + def clean_password(self): 37 + password = self.cleaned_data.get('password') 38 + username = self.cleaned_data.get('username') 39 + try: 40 + user = User.objects.get(username=username) 41 + if not user.check_password(password): 42 + raise forms.ValidationError(_("Password incorrect")) 43 + except User.DoesNotExist: 44 + pass 45 + return password 46 + 47 + 48 + class UserForm(forms.ModelForm): 49 + 50 + class Meta: 51 + model = User 52 + fields = ["first_name", "last_name", "email", "default_address", "default_position", "default_distance"]
+2 -1
upvcarshare/users/migrations/0001_initial.py
··· 1 1 # -*- coding: utf-8 -*- 2 - # Generated by Django 1.9.5 on 2016-05-16 13:04 2 + # Generated by Django 1.9.5 on 2016-05-18 12:09 3 3 from __future__ import unicode_literals 4 4 5 5 import django.contrib.auth.models ··· 33 33 ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 34 34 ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 35 35 ('default_address', models.TextField(blank=True, null=True)), 36 + ('default_distance', models.PositiveIntegerField(blank=True, default=500, null=True)), 36 37 ('default_position', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=2062)), 37 38 ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 38 39 ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
+24 -1
upvcarshare/users/models.py
··· 4 4 from django.contrib.auth.base_user import AbstractBaseUser 5 5 from django.contrib.auth.models import PermissionsMixin, AbstractUser, UserManager 6 6 from django.contrib.gis.db import models 7 + from django.contrib.gis.gdal import SpatialReference, CoordTransform 7 8 from django.utils.translation import ugettext_lazy as _ 8 9 from rest_framework.authtoken.models import Token 9 10 10 - from journeys import DEFAULT_PROJECTED_SRID 11 + from journeys import DEFAULT_PROJECTED_SRID, DEFAULT_DISTANCE, DEFAULT_WGS84_SRID 11 12 12 13 13 14 class User(AbstractUser): 14 15 """Custom user model.""" 15 16 default_address = models.TextField(null=True, blank=True) 17 + 18 + default_distance = models.PositiveIntegerField(null=True, blank=True, default=DEFAULT_DISTANCE) 16 19 default_position = models.PointField(null=True, blank=True, srid=DEFAULT_PROJECTED_SRID) 17 20 18 21 objects = UserManager() ··· 20 23 class Meta: 21 24 verbose_name = _('user') 22 25 verbose_name_plural = _('users') 26 + 27 + def get_default_position_wgs84(self): 28 + """Transforms position to WGS-84 system.""" 29 + if self.default_position is None: 30 + return None 31 + destination_coord = SpatialReference(DEFAULT_WGS84_SRID) 32 + origin_coord = SpatialReference(DEFAULT_PROJECTED_SRID) 33 + trans = CoordTransform(origin_coord, destination_coord) 34 + position = self.default_position 35 + position.transform(trans) 36 + return position 37 + 38 + def set_default_position_wgs84(self, position): 39 + """Transforms an input to projected coordinates.""" 40 + destination_coord = SpatialReference(DEFAULT_PROJECTED_SRID) 41 + origin_coord = SpatialReference(DEFAULT_WGS84_SRID) 42 + trans = CoordTransform(origin_coord, destination_coord) 43 + position.transform(trans) 44 + self.default_position = position 45 + return self.default_position 23 46 24 47 def save(self, *args, **kwargs): 25 48 """Override to create API Token."""
+2
upvcarshare/users/tests/factories.py
··· 2 2 from __future__ import unicode_literals, print_function, absolute_import 3 3 4 4 import factory 5 + from django.contrib.auth.hashers import make_password 5 6 6 7 7 8 class UserFactory(factory.django.DjangoModelFactory): 8 9 9 10 username = factory.Sequence(lambda n: 'foo%s' % n) 11 + password = make_password("password") 10 12 11 13 class Meta: 12 14 model = "users.User"
+42
upvcarshare/users/tests/test_api.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + import six 5 + from django.contrib.gis.geos import Point 6 + from rest_framework import status 7 + from rest_framework.test import APITestCase 8 + 9 + from journeys import DEFAULT_PROJECTED_SRID 10 + from users.models import User 11 + from users.tests.factories import UserFactory 12 + 13 + 14 + class UsersAPITests(APITestCase): 15 + 16 + def setUp(self): 17 + self.user = UserFactory(default_position=Point(882823.07, 545542.48, srid=DEFAULT_PROJECTED_SRID)) 18 + 19 + def test_get_current_user(self): 20 + url = "/api/v1/users/me/" 21 + self.client.force_authenticate(user=self.user) 22 + response = self.client.get(url) 23 + self.assertEqual(response.status_code, status.HTTP_200_OK) 24 + 25 + def test_update_current_user(self): 26 + url = "/api/v1/users/me/" 27 + self.client.force_authenticate(user=self.user) 28 + data = { 29 + "default_address": "foo", 30 + "default_position": { 31 + "type": "Point", 32 + "coordinates": [-0.37677, 39.46914] 33 + } 34 + } 35 + response = self.client.patch(url, data, format="json") 36 + self.assertEqual(response.status_code, status.HTTP_200_OK) 37 + user = User.objects.get(pk=self.user.pk) 38 + self.assertEquals( 39 + "SRID=4326;POINT (-0.3767699999989403 39.4691399999970258)", 40 + six.text_type(user.get_default_position_wgs84()) 41 + ) 42 + self.assertEquals("foo", user.default_address)
+38
upvcarshare/users/tests/test_forms.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from test_plus import TestCase 5 + 6 + from users.forms import SignInForm 7 + from users.tests.factories import UserFactory 8 + 9 + 10 + class SigInFormTests(TestCase): 11 + user_factory = UserFactory 12 + 13 + def test_sign_in_form(self): 14 + user = self.make_user(username="foo") 15 + data = { 16 + "username": user.username, 17 + "password": "password" 18 + } 19 + form = SignInForm(data) 20 + self.assertTrue(form.is_valid()) 21 + 22 + def test_sign_in_bad_password_form(self): 23 + user = self.make_user(username="foo") 24 + data = { 25 + "username": user.username, 26 + "password": "bad" 27 + } 28 + form = SignInForm(data) 29 + self.assertFalse(form.is_valid()) 30 + 31 + def test_sign_in_bad_username_form(self): 32 + self.make_user(username="foo") 33 + data = { 34 + "username": "bad", 35 + "password": "password" 36 + } 37 + form = SignInForm(data) 38 + self.assertFalse(form.is_valid())
upvcarshare/users/tests/test_models.py upvcarshare/pages/models.py
+61
upvcarshare/users/tests/test_views.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from test_plus import TestCase 5 + 6 + from users.models import User 7 + from users.tests.factories import UserFactory 8 + 9 + 10 + class SigInViewTests(TestCase): 11 + user_factory = UserFactory 12 + 13 + def test_get_sign_in(self): 14 + response = self.get("users:sign-in") 15 + self.response_200(response=response) 16 + 17 + def test_post_sign_in(self): 18 + user = self.make_user(username="foo") 19 + data = { 20 + "username": user.username, 21 + "password": "password" 22 + } 23 + response = self.post(url_name="users:sign-in", data=data) 24 + self.response_302(response=response) 25 + 26 + def test_post_bad_sign_in(self): 27 + user = self.make_user(username="foo") 28 + data = { 29 + "username": user.username, 30 + "password": "bad" 31 + } 32 + response = self.post(url_name="users:sign-in", data=data) 33 + self.response_200(response=response) 34 + 35 + 36 + class EditProfileViewTests(TestCase): 37 + user_factory = UserFactory 38 + 39 + def setUp(self): 40 + self.user = self.make_user(username="foo") 41 + 42 + def test_get_edit_profile(self): 43 + url_name = "users:edit" 44 + self.assertLoginRequired(url_name) 45 + with self.login(self.user): 46 + response = self.get(url_name) 47 + self.response_200(response=response) 48 + 49 + def test_post_edit_profile(self): 50 + url_name = "users:edit" 51 + self.assertLoginRequired(url_name) 52 + with self.login(self.user): 53 + data = { 54 + "first_name": "foo", 55 + "last_name": "bar" 56 + } 57 + response = self.post(url_name, data=data) 58 + self.response_200(response=response) 59 + user = User.objects.get(pk=self.user.pk) 60 + self.assertEquals(data["first_name"], user.first_name) 61 + self.assertEquals(data["last_name"], user.last_name)
+11
upvcarshare/users/urls.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django.conf.urls import url 5 + 6 + from users.views import SignInView, EditProfileView 7 + 8 + urlpatterns = [ 9 + url(r"sign-in/$", SignInView.as_view(), name="sign-in"), 10 + url(r"edit/$", EditProfileView.as_view(), name="edit"), 11 + ]
+69
upvcarshare/users/views.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from braces.views import CsrfExemptMixin 5 + from braces.views import LoginRequiredMixin 6 + from django.contrib.auth import authenticate, login 7 + from django.core.urlresolvers import reverse 8 + from django.shortcuts import render, redirect 9 + from django.views.generic import View 10 + 11 + from users.forms import SignInForm, UserForm 12 + 13 + 14 + class SignInView(CsrfExemptMixin, View): 15 + """View to allow to login users into the platform.""" 16 + template_name = "users/sign_in.html" 17 + 18 + @staticmethod 19 + def get_next_page(request): 20 + """Gets the page to go after log in.""" 21 + default_redirect = reverse('home') 22 + next_page = request.GET.get('next') or request.POST.get('next') 23 + return next_page or default_redirect 24 + 25 + def get(self, request): 26 + data = { 27 + "form": SignInForm(), 28 + "next": request.GET.get('next') 29 + } 30 + return render(request, self.template_name, data) 31 + 32 + def post(self, request): 33 + next_page = self.get_next_page(request=request) 34 + form = SignInForm(request.POST) 35 + if form.is_valid(): 36 + user = authenticate( 37 + username=form.cleaned_data.get('username'), 38 + password=form.cleaned_data.get('password'), 39 + request=request 40 + ) 41 + if user is not None: 42 + login(request, user) 43 + return redirect(next_page) 44 + data = {"form": form, "next": next_page} 45 + return render(request, self.template_name, data) 46 + 47 + 48 + class EditProfileView(LoginRequiredMixin, View): 49 + """View to edit profile of the current user.""" 50 + 51 + template_name = "users/edit.html" 52 + 53 + def get(self, request): 54 + form = UserForm(instance=request.user) 55 + data = { 56 + "form": form 57 + } 58 + return render(request, self.template_name, data) 59 + 60 + def post(self, request): 61 + form = UserForm(request.POST, instance=request.user) 62 + data = { 63 + "form": form 64 + } 65 + print(form.errors) 66 + if form.is_valid(): 67 + form.save() 68 + return render(request, self.template_name, data) 69 +