# HG changeset patch # User Lennard de Rijk # Date 1229124498 0 # Node ID ba3d399ec9be734e8123187a39290f7ba5c48bef # Parent 6180b32d990fb03d2f07027b05c9816af9eb7d83 Added Notifications. Notifications will primarily be used to serve the user information message like "New Request Pending". The system can be expanded later on to a messaging system. If you are a developer you can create message by going to notification/create . This has been done so the system can easily be tested. But will of course be visible in an easy-to-access location when the transition to a message system has been made. Patch by: Lennard de Rijk diff -r 6180b32d990f -r ba3d399ec9be app/soc/logic/models/notification.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/logic/models/notification.py Fri Dec 12 23:28:18 2008 +0000 @@ -0,0 +1,42 @@ +#!/usr/bin/python2.5 +# +# Copyright 2008 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Notification (Model) query functions. +""" + +__authors__ = [ + '"Lennard de Rijk" ', + ] + + +from soc.logic.models import base +from soc.logic.models import user as user_logic + +import soc.models.notification + + +class Logic(base.Logic): + """Logic methods for the Notification model. + """ + + def __init__(self): + """Defines the name, key_name and model for this entity. + """ + super(Logic, self).__init__(model=soc.models.notification.Notification, + base_model=None, scope_logic=user_logic) + + +logic = Logic() diff -r 6180b32d990f -r ba3d399ec9be app/soc/models/notification.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/models/notification.py Fri Dec 12 23:28:18 2008 +0000 @@ -0,0 +1,58 @@ +#!/usr/bin/python2.5 +# +# Copyright 2008 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module contains the Notification Model. +""" + +__authors__ = [ + '"Lennard de Rijk" ', +] + +from google.appengine.ext import db + +from django.utils.translation import ugettext_lazy + +from soc.models import base + +import soc.models.linkable +import soc.models.user + + +class Notification(soc.models.linkable.Linkable): + """Model of a Notification. + """ + + # a reference to the user this Notification is from + # this is a non-required property, None will indicate an Anonymous Admin + from_user = db.ReferenceProperty(reference_class=soc.models.user.User, + required=False, + collection_name="sent_notifications", + verbose_name=ugettext_lazy('From User')) + + subject = db.StringProperty(required=True, + verbose_name=ugettext_lazy('Subject')) + + # the message that is contained within this Notification + message = db.TextProperty(required=True, + verbose_name=ugettext_lazy('Message')) + + # date and time on which this Notification was created + created_on = db.DateTimeProperty(auto_now_add=True, + verbose_name=ugettext_lazy('Created On')) + + # boolean property that marks if the notification has been read + has_been_read = db.BooleanProperty(default=False, + verbose_name=ugettext_lazy('Read')) diff -r 6180b32d990f -r ba3d399ec9be app/soc/templates/soc/notification/list/heading.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/notification/list/heading.html Fri Dec 12 23:28:18 2008 +0000 @@ -0,0 +1,5 @@ + + From + Subject + Received on + diff -r 6180b32d990f -r ba3d399ec9be app/soc/templates/soc/notification/list/row.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/notification/list/row.html Fri Dec 12 23:28:18 2008 +0000 @@ -0,0 +1,6 @@ + +
{{ list.item.from_user.name }}
+
{{ list.item.subject }}
+
{{ list.item.created_on }}
+ diff -r 6180b32d990f -r ba3d399ec9be app/soc/templates/soc/notification/public.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/notification/public.html Fri Dec 12 23:28:18 2008 +0000 @@ -0,0 +1,40 @@ +{% extends "soc/base.html" %} +{% comment %} +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% endcomment %} + +{% block page_title %} +Notification +{% endblock %} + +{% block header_title %} +Notification about: {{ entity.subject }} +{% endblock %} + +{% block body %} +From: +{% if not entity.from_user %} + Anonymous +{% else %} + {{ entity.from_user.name }} +{% endif %}
+To: {{ entity.scope.name }}
+Sent On: {{ entity.created_on }}
+Subject: {{ entity.subject }}
+Message:
+{{ entity.message }} +

+ + + +{% endblock %} diff -r 6180b32d990f -r ba3d399ec9be app/soc/views/helper/access.py --- a/app/soc/views/helper/access.py Fri Dec 12 00:35:51 2008 +0000 +++ b/app/soc/views/helper/access.py Fri Dec 12 23:28:18 2008 +0000 @@ -26,6 +26,7 @@ __authors__ = [ '"Todd Larsen" ', '"Sverre Rabbelier" ', + '"Lennard de Rijk" ', '"Pawel Solyga" ', ] @@ -38,6 +39,7 @@ from soc.logic import accounts from soc.logic import dicts from soc.logic.models import host as host_logic +from soc.logic.models import notification as notification_logic from soc.logic.models import user as user_logic from soc.logic.models import request as request_logic from soc.views import helper @@ -318,6 +320,54 @@ raise out_of_band.LoginRequest(message_fmt=login_message_fmt) +def checkIsMyNotification(request): + """Returns an alternate HTTP response if this request is for a Notification belonging + to the current user. + + Args: + request: a Django HTTP request + + Raises: + AccessViolationResponse: if the required authorization is not met + + Returns: + None if the current User is allowed to access this Notification. + """ + + try: + # if the current user is a developer we allow access + checkIsDeveloper(request) + return + except out_of_band.Error: + pass + + checkIsUser(request) + + splitpath = request.path.split('/') + splitpath = splitpath[1:] # cut off leading '' + + # get the notification scope (user link_id) from the request path + user_link_id = splitpath[2] + # get the notification link_id from the request path + notification_link_id = splitpath[3] + + properties = { + 'link_id': notification_link_id, + 'scope_path': user_link_id, + } + + notification = notification_logic.logic.getForFields(properties, unique=True) + + user = user_logic.logic.getForFields( + {'account': users.get_current_user()}, unique=True) + + # check if the key of the current user matches the key from the scope of the message + if user.key() == notification.scope.key(): + # access granted + return None + else: + # access denied + deny(request) def checkCanInvite(request): """Checks to see if the current user can create an invite @@ -360,7 +410,6 @@ # Perform the access check helper.access.checkAccess(access_type, request, rights=params['rights']) - def checkIsDocumentPublic(request): """Checks whether a document is public. diff -r 6180b32d990f -r ba3d399ec9be app/soc/views/models/notification.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/views/models/notification.py Fri Dec 12 23:28:18 2008 +0000 @@ -0,0 +1,244 @@ +#!/usr/bin/python2.5 +# +# Copyright 2008 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module contains the view code for Notifications +""" + +__authors__ = [ + '"Lennard de Rijk" ', +] + + +import time + +from google.appengine.api import users + +from django import forms +from django import http +from django.utils.translation import ugettext_lazy + +from soc.logic import dicts +from soc.logic import validate +from soc.models import notification as notification_model +from soc.views import helper +from soc.views import out_of_band +from soc.views.helper import access +from soc.views.helper import redirects +from soc.views.models import base +from soc.logic.models import notification as notification_logic +from soc.logic.models import user as user_logic + + +class CreateForm(helper.forms.BaseForm): + """Form for creating a Notification. + """ + + # to user field + to_user = forms.fields.CharField(label='To User') + + def __init__(self, *args, **kwargs): + """ Calls super and then redefines the order in which the fields appear. + + for parameters see BaseForm.__init__() + """ + super(CreateForm, self).__init__(*args, **kwargs) + + # set form fields order + self.fields.keyOrder = ['to_user', 'subject', 'message'] + + class Meta: + model = notification_model.Notification + + # exclude the necessary fields from the form + exclude = ['link_id', 'scope', 'scope_path', 'from_user', 'has_been_read'] + + def clean_to_user(self): + """Check if the to_user field has been filled in correctly. + """ + link_id = self.cleaned_data.get('to_user').lower() + + if not validate.isLinkIdFormatValid(link_id): + raise forms.ValidationError("This link ID is in wrong format.") + + to_user = user_logic.logic.getForFields({'link_id' : link_id}, unique=True) + + if not to_user: + # user does not exist + raise forms.ValidationError("This user does not exist") + + return link_id + + +class View(base.View): + """View methods for the Notification model. + """ + + def __init__(self, params=None): + """Defines the fields and methods required for the base View class + to provide the user with list, public, create, edit and delete views. + + Params: + params: a dict with params for this View + """ + + new_params = {} + new_params['logic'] = notification_logic.logic + + new_params['name'] = "Notification" + new_params['name_short'] = "Notification" + new_params['name_plural'] = "Notifications" + new_params['url_name'] = "notification" + new_params['module_name'] = "notification" + + new_params['create_form'] = CreateForm + + # define the django url patterns + new_params['django_patterns_defaults'] = [ + (r'^%(url_name)s/(?Pshow)/%(key_fields)s$', + 'soc.views.models.%(module_name)s.public', 'Show %(name_short)s'), + (r'^%(url_name)s/(?Pcreate)$', + 'soc.views.models.%(module_name)s.create', 'Create %(name_short)s'), + (r'^%(url_name)s/(?Pcreate)/%(scope)s$', + 'soc.views.models.%(module_name)s.create', 'Create %(name_short)s'), + (r'^%(url_name)s/(?Pdelete)/%(key_fields)s$', + 'soc.views.models.%(module_name)s.delete', 'Delete %(name_short)s'), + (r'^%(url_name)s/(?Plist)$', + 'soc.views.models.%(module_name)s.list', 'List %(name_plural)s'), + ] + + rights = {} + rights['unspecified'] = [access.deny] + rights['any_access'] = [access.allow] + rights['show'] = [access.checkIsMyNotification] + rights['delete'] = [access.checkIsDeveloper] + rights['list'] = [access.checkIsUser] + # create is developer only for the time being to test functionality + rights['create'] = [access.checkIsDeveloper] + + new_params['rights'] = rights + + params = dicts.merge(params, new_params) + + super(View, self).__init__(params=params) + + def create(self, request, access_type, + page_name=None, params=None, **kwargs): + """On a successful post create redirects the user to the notification list. + + for parameters see base.create() + """ + + if request.method == 'POST': + response = super(View, self).create(request, access_type, + page_name, params, **kwargs) + + if (response.__class__ == http.HttpResponseRedirect and + response['location'].startswith( + '/%s/edit/' %(self._params['url_name']))): + # redirect to list instead of edit view + return http.HttpResponseRedirect('/%s/list' %(self._params['url_name'])) + else: + return response + + else: + # request.method == 'GET' so act normal + return super(View, self).create(request, access_type, + page_name, params, **kwargs) + + def list(self, request, access_type, + page_name=None, params=None, seed=None, **kwargs): + """Lists all notifications that the current logged in user has stored. + + for parameters see base.list() + """ + + params = dicts.merge(params, self._params) + + # get the current user + properties = {'account': users.get_current_user()} + user_entity = user_logic.logic.getForFields(properties, unique=True) + + # only select the notifications for this user so construct a filter + filter = {'scope': user_entity} + + # create the list parameters + list_params = params.copy() + + # define the list redirect action to show the notification + list_params['list_action'] = (redirects.getPublicRedirect, params) + list_params['list_description'] = ugettext_lazy( + "An overview of your received Notifications.") + + # TODO(Lennard) when list sorting is implemented sort on descending date + + # use the generic list method with the filter. The access check in this + # method will trigger an errorResponse when user_entity is None + return super(View, self).list(request, access_type, + page_name, list_params, filter) + + def _editPost(self, request, entity, fields): + """See base.View._editPost(). + """ + + account = users.get_current_user() + current_user = user_logic.logic.getForFields({'account': account}, unique=True) + + to_user = user_logic.logic.getForFields( + {'link_id' : fields['to_user']}, unique=True) + + fields['link_id'] = '%i' %(time.time()) + fields['scope'] = to_user + fields['from_user'] = current_user + fields['scope_path'] = fields['to_user'] + + def _editSeed(self, request, seed): + """Checks if scope_path is seeded and puts it into to_user. + + for parameters see base._editSeed() + """ + + # if scope_path is present + if 'scope_path' in seed.keys(): + # fill the to_user field with the scope path + seed['to_user'] = seed['scope_path'] + + def _public(self, request, entity, context): + """Marks the Notification as read if that hasn't happened yet. + + for parameters see base._public() + """ + + # if the user viewing is the user for which this notification is meant + # and the notification has not been read yet + if not entity.has_been_read: + # get the current user + account = users.get_current_user() + user = user_logic.logic.getForFields({'account': account}, unique=True) + + if entity.scope.key() == user.key(): + # mark the entity as read + self._logic.updateModelProperties(entity, {'has_been_read' : True} ) + + context['entity_type_url'] = self._params['url_name'] + context['entity_suffix'] = self._logic.getKeySuffix(entity) + + +view = View() +create = view.create +edit = view.edit +delete = view.delete +list = view.list +public = view.public diff -r 6180b32d990f -r ba3d399ec9be app/soc/views/models/user_self.py --- a/app/soc/views/models/user_self.py Fri Dec 12 00:35:51 2008 +0000 +++ b/app/soc/views/models/user_self.py Fri Dec 12 23:28:18 2008 +0000 @@ -100,6 +100,7 @@ rights['edit'] = [access.checkIsLoggedIn] rights['roles'] = [access.checkIsUser] rights['signIn'] = [access.checkNotLoggedIn] + rights['notification'] = [access.checkIsUser] new_params = {} new_params['rights'] = rights @@ -116,6 +117,7 @@ (users.create_login_url("user/edit"), 'Sign In', 'signIn'), ('/' + new_params['url_name'] + '/edit', 'Profile', 'edit'), ('/' + new_params['url_name'] + '/roles', 'Roles', 'roles'), + ('/' + 'notification/list', 'Notifications', 'notification'), ] patterns = [] @@ -127,9 +129,9 @@ page_name = "Requests Overview" patterns += [(r'^%(url_name)s/(?Proles)$', 'soc.views.models.request.list_self', page_name)] - + new_params['django_patterns_defaults'] = patterns - + params = dicts.merge(params, new_params) super(View, self).__init__(params=params) diff -r 6180b32d990f -r ba3d399ec9be app/soc/views/sitemap/build.py --- a/app/soc/views/sitemap/build.py Fri Dec 12 00:35:51 2008 +0000 +++ b/app/soc/views/sitemap/build.py Fri Dec 12 23:28:18 2008 +0000 @@ -26,6 +26,7 @@ from soc.views.models import document from soc.views.models import host +from soc.views.models import notification from soc.views.models import organization from soc.views.models import presence from soc.views.models import program @@ -55,6 +56,7 @@ sitemap.addPages(site.view.getDjangoURLPatterns()) sitemap.addPages(user.view.getDjangoURLPatterns()) sitemap.addPages(user_self.view.getDjangoURLPatterns()) +sitemap.addPages(notification.view.getDjangoURLPatterns()) sitemap.addPages(document.view.getDjangoURLPatterns()) sitemap.addPages(sponsor.view.getDjangoURLPatterns()) sitemap.addPages(host.view.getDjangoURLPatterns())