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.

notifications system

+237 -14
+1
upvcarshare/config/settings/base.py
··· 162 162 'pages', 163 163 'users', 164 164 'journeys', 165 + 'notifications', 165 166 ) 166 167 167 168 INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS
+7 -6
upvcarshare/journeys/managers.py
··· 67 67 data["free_places"] = transport.default_places 68 68 return self.create(**data) 69 69 70 - def available(self, kind=None): 70 + def available(self, kind=None, ignore_full=False): 71 71 """Gets all available journeys. 72 72 :param kind: GOING, RETURN 73 + :param ignore_full: 73 74 """ 74 75 now = timezone.now() 75 76 queryset = self.filter(driver__isnull=False, departure__gt=now) 76 77 if kind is not None: 77 78 queryset = queryset.filter(kind=kind) 79 + if ignore_full: 80 + return queryset 78 81 return queryset.\ 79 82 annotate(total_passengers=Count("passengers")).\ 80 83 filter(total_passengers__lt=F("free_places")) ··· 109 112 queryset = queryset.filter(kind=kind) 110 113 return queryset 111 114 112 - def recommended(self, user, kind=None, journey=None, override_distance=None, exclude_fulfilled=False): 115 + def recommended(self, user, kind=None, journey=None, override_distance=None, ignore_full=False): 113 116 """Gets the journeys recommended for an user needs. 114 117 :param user: 115 118 :param kind: 116 119 :param journey: 117 120 :param override_distance: 118 - :param exclude_fulfilled: 121 + :param ignore_full: 119 122 """ 120 123 # Gets journeys needed by the user... 121 124 if journey is None: ··· 128 131 if not conditions: 129 132 return self.none() 130 133 now = timezone.now() 131 - queryset = self.available(kind=kind).exclude(user=user, departure__lt=now)\ 134 + queryset = self.available(kind=kind, ignore_full=ignore_full).exclude(user=user, departure__lt=now)\ 132 135 .filter(reduce(lambda x, y: x | y, conditions))\ 133 136 .order_by("departure") 134 - if exclude_fulfilled: 135 - queryset = queryset.exclude(passengers__user=user) 136 137 return queryset 137 138 138 139 def passenger(self, user):
+9 -5
upvcarshare/journeys/models.py
··· 19 19 from journeys.exceptions import NoFreePlaces, NotAPassenger, AlreadyAPassenger 20 20 from journeys.helpers import make_point_wgs84 21 21 from journeys.managers import JourneyManager, ResidenceManager 22 + from notifications import JOIN, LEAVE 23 + from notifications.decorators import dispatch 22 24 23 25 24 26 class Place(GisTimeStampedModel): ··· 177 179 return self.free_places - self.count_passengers() 178 180 return 0 179 181 182 + @dispatch(JOIN) 180 183 def join_passenger(self, user): 181 184 """A user joins a journey. 182 185 :param user: ··· 190 193 ) 191 194 raise NoFreePlaces() 192 195 196 + @dispatch(LEAVE) 193 197 def leave_passenger(self, user): 194 198 """A user joins a journey. 195 199 :param user: ··· 202 206 """Checks if the given user is a passenger of this journey.""" 203 207 return self.passengers.filter(user=user).exists() 204 208 205 - def recommended(self): 209 + def recommended(self, ignore_full=False): 206 210 """Gets recommended journeys for this journey. 207 211 :returns QuerySet: 208 212 """ 209 213 if self.driver == self.user: 210 214 return Journey.objects.none() 211 - return Journey.objects.recommended(user=self.user, kind=self.kind, journey=self) 215 + return Journey.objects.recommended(user=self.user, kind=self.kind, journey=self, ignore_full=ignore_full) 212 216 213 217 def needs_driver(self): 214 218 """Checks if the journey needs a driver.""" ··· 220 224 221 225 def is_fulfilled(self): 222 226 """Check if the journey is already fulfilled by the given user.""" 223 - return self.needs_driver() and self.recommended().filter(passengers__user=self.user).exists() 227 + return self.needs_driver() and self.recommended(ignore_full=True).filter(passengers__user=self.user).exists() 224 228 225 229 def fulfilled_by(self): 226 - """Gets the journey who if fulfilling this one.""" 230 + """Gets journeys who are fulfilling this one.""" 227 231 if not self.is_fulfilled(): 228 232 return None 229 - return self.recommended().filter(passengers__user=self.user).first() 233 + return self.recommended(ignore_full=True).filter(passengers__user=self.user) 230 234 231 235 def distance(self): 232 236 """Gets the journey distance."""
-1
upvcarshare/journeys/views.py
··· 165 165 "journeys": Journey.objects.recommended( 166 166 user=request.user, 167 167 kind=kind_filter, 168 - exclude_fulfilled=True, 169 168 override_distance=override_distance 170 169 ), 171 170 }
+5
upvcarshare/notifications/__init__.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + # Notification verbs 5 + JOIN, LEAVE = "join", "leave"
+12
upvcarshare/notifications/admin.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django.contrib.gis import admin 5 + 6 + from notifications.models import Notification 7 + 8 + 9 + @admin.register(Notification) 10 + class NotificationAdmin(admin.ModelAdmin): 11 + list_display = ["id", "user", "actor", "verb", "target", "action", "read", "created"] 12 + list_filter = ["user"]
+28
upvcarshare/notifications/decorators.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from notifications.models import Notification 5 + 6 + 7 + def dispatch(verb): 8 + """Creates decorator for a given verb. 9 + :param verb: 10 + :return: 11 + """ 12 + def _decorator(function): 13 + """Decorator itself. 14 + :param function: 15 + :return: 16 + """ 17 + def _wrapper_dispatch(*args, **kwargs): 18 + """Wrapped function with the decorator. 19 + :param args: 20 + :param kwargs: 21 + :return: 22 + """ 23 + result = function(*args, **kwargs) 24 + # Creates the notification after call the function. 25 + Notification.objects.create_from_method_call(verb=verb, function=function, args=args, kwargs=kwargs) 26 + return result 27 + return _wrapper_dispatch 28 + return _decorator
+44
upvcarshare/notifications/manager.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + from django.contrib.auth import get_user_model 5 + from django.db import models 6 + from django.utils.functional import SimpleLazyObject 7 + 8 + from notifications import LEAVE, JOIN 9 + 10 + 11 + def extract(classes, iterable): 12 + """Extracts objects that are instances of 'classes' from 13 + the given iterable. 14 + """ 15 + if classes.__class__ not in (list, tuple): 16 + classes = (classes, ) 17 + return list(filter(lambda item: item.__class__ in classes, iterable)) 18 + 19 + 20 + class NotificationManager(models.Manager): 21 + 22 + def create_from_method_call(self, verb, function, args, kwargs): 23 + """Creates a notification from a decorator call. 24 + :param verb: 25 + :param function: 26 + :param args: 27 + :param kwargs: 28 + """ 29 + from journeys.models import Journey 30 + 31 + objects = list(args) + list(kwargs.values()) 32 + 33 + # User ('actor') leaves|joins ('verb') journey ('target') 34 + if verb in (JOIN, LEAVE): 35 + notification = self.model(verb=verb) 36 + actor = extract([get_user_model(), SimpleLazyObject], objects)[0] 37 + journey = extract(Journey, objects)[0] 38 + notification.user = journey.user 39 + notification.actor = actor 40 + notification.target = journey 41 + notification.save() 42 + return notification 43 + 44 + return None
+43
upvcarshare/notifications/migrations/0001_initial.py
··· 1 + # -*- coding: utf-8 -*- 2 + # Generated by Django 1.9.5 on 2016-05-26 11:30 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 + 10 + 11 + class Migration(migrations.Migration): 12 + 13 + initial = True 14 + 15 + dependencies = [ 16 + migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 + ('contenttypes', '0002_remove_content_type_name'), 18 + ] 19 + 20 + operations = [ 21 + migrations.CreateModel( 22 + name='Notification', 23 + fields=[ 24 + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 + ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')), 26 + ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')), 27 + ('actor_object_id', models.PositiveIntegerField()), 28 + ('verb', models.CharField(max_length=255)), 29 + ('target_object_id', models.PositiveIntegerField(blank=True, null=True)), 30 + ('action_object_id', models.PositiveIntegerField(blank=True, null=True)), 31 + ('read', models.BooleanField(default=False)), 32 + ('action_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_action', to='contenttypes.ContentType')), 33 + ('actor_content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notify_actor', to='contenttypes.ContentType')), 34 + ('target_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_target', to='contenttypes.ContentType')), 35 + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)), 36 + ], 37 + options={ 38 + 'abstract': False, 39 + 'ordering': ('-modified', '-created'), 40 + 'get_latest_by': 'modified', 41 + }, 42 + ), 43 + ]
upvcarshare/notifications/migrations/__init__.py

This is a binary file and will not be displayed.

+76
upvcarshare/notifications/models.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import 3 + 4 + import six 5 + from django.conf import settings 6 + from django.contrib.contenttypes.fields import GenericForeignKey 7 + from django.contrib.contenttypes.models import ContentType 8 + from django.db import models 9 + from django.templatetags.l10n import localize 10 + from django.utils.html import strip_tags 11 + from django.utils.safestring import mark_safe 12 + from django.utils.six import python_2_unicode_compatible 13 + from django.utils.translation import ugettext_lazy as _ 14 + from django_extensions.db.models import TimeStampedModel 15 + 16 + from notifications import JOIN, LEAVE 17 + from notifications.manager import NotificationManager 18 + 19 + 20 + @python_2_unicode_compatible 21 + class Notification(TimeStampedModel): 22 + """A notification is a record of an action on the system. The 'actor' makes an 'action' (optional), according 23 + to a 'verb' over a 'target' (optional). 24 + 25 + <actor> <verb> <time> 26 + <actor> <verb> <target> <time> 27 + <actor> <verb> <action> <target> <time> 28 + 29 + Based on: http://activitystrea.ms/specs/atom/1.0/ 30 + """ 31 + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notifications') 32 + 33 + # - Actor 34 + actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor') 35 + actor_object_id = models.PositiveIntegerField() 36 + actor = GenericForeignKey('actor_content_type', 'actor_object_id') 37 + 38 + # - Verb 39 + verb = models.CharField(max_length=255) 40 + 41 + # - Target 42 + target_content_type = models.ForeignKey(ContentType, related_name='notify_target', blank=True, null=True) 43 + target_object_id = models.PositiveIntegerField(blank=True, null=True) 44 + target = GenericForeignKey('target_content_type', 'target_object_id') 45 + 46 + # - Action object 47 + action_content_type = models.ForeignKey(ContentType, related_name='notify_action', blank=True, null=True) 48 + action_object_id = models.PositiveIntegerField(blank=True, null=True) 49 + action = GenericForeignKey('action_content_type', 'action_object_id') 50 + 51 + read = models.BooleanField(default=False, blank=False) 52 + 53 + objects = NotificationManager() 54 + 55 + def __str__(self): 56 + return self.text(strip_html=True) 57 + 58 + def text(self, strip_html=False): 59 + """Creates the text representation of the notification.""" 60 + value = "" 61 + if self.verb == JOIN: 62 + # actor is a user and target is a journey 63 + value = _("%(user)s se ha <strong>unido</strong> al trayecto <strong>%(journey)s</strong> del %(date)s") % { 64 + "user": six.text_type(self.actor), 65 + "journey": six.text_type(self.target).lower(), 66 + "date": localize(self.target.departure), 67 + } 68 + elif self.verb == LEAVE: 69 + value = _("%(user)s ha <strong>abandonado</strong> el trayecto <strong>%(journey)s</strong> del %(date)s") % { 70 + "user": six.text_type(self.actor), 71 + "journey": six.text_type(self.target).lower(), 72 + "date": localize(self.target.departure), 73 + } 74 + if strip_html: 75 + value = strip_tags(value) 76 + return mark_safe(value)
+2
upvcarshare/notifications/tests/__init__.py
··· 1 + # -*- coding: utf-8 -*- 2 + from __future__ import unicode_literals, print_function, absolute_import
+4 -2
upvcarshare/templates/journeys/details.html
··· 44 44 </table> 45 45 {% elif is_fulfilled %} 46 46 <h3>{% trans "Trayecto enlazado" %}</h3> 47 - <p class="text-muted">{% trans "Te has apuntado a este trayecto de otro usuario para cubrir tu necesiodad." %}</p> 48 - {% journey_item fulfilled_by %} 47 + <p class="text-muted">{% trans "Te has apuntado a estos trayectos de otros usuarios para cubrir tu necesidad." %}</p> 48 + {% for fulfilled_journey in fulfilled_by %} 49 + {% journey_item fulfilled_journey %} 50 + {% endfor %} 49 51 {% else %} 50 52 <h3>{% trans "Trayectos recomendados" %}</h3> 51 53 <p class="text-muted">{% trans "Otros trayectos ofrecidos por otros usuarios que encajan en tus criterios." %}</p>
+1
upvcarshare/templates/journeys/templatetags/item.html
··· 12 12 <div class="col-sm-7"> 13 13 <h4> 14 14 {% if journey_item.disabled%}<span class="label label-danger">{% trans "Cancelado" %}</span>{% endif %} 15 + {% if journey_item|is_passenger:request.user %}<span class="label label-success">{% trans "Apuntado" %}</span>{% endif %} 15 16 {{ journey_item.departure }} 16 17 </h4> 17 18 <p class="lead">{{ journey_item.description }}</p>
+5
upvcarshare/users/models.py
··· 5 5 from django.contrib.auth.models import PermissionsMixin, AbstractUser, UserManager 6 6 from django.contrib.gis.db import models 7 7 from django.contrib.gis.gdal import SpatialReference, CoordTransform 8 + from django.utils.six import python_2_unicode_compatible 8 9 from django.utils.translation import ugettext_lazy as _ 9 10 from rest_framework.authtoken.models import Token 10 11 11 12 from journeys import DEFAULT_PROJECTED_SRID, DEFAULT_DISTANCE, DEFAULT_WGS84_SRID 12 13 13 14 15 + @python_2_unicode_compatible 14 16 class User(AbstractUser): 15 17 """Custom user model.""" 16 18 default_address = models.TextField( ··· 39 41 class Meta: 40 42 verbose_name = _('user') 41 43 verbose_name_plural = _('users') 44 + 45 + def __str__(self): 46 + return self.get_full_name() 42 47 43 48 def get_full_name(self): 44 49 """Returns the first_name plus the last_name, with a space in between. In case there is no name,