From 18fb871917ea875ed8a66c017947c2678d71f5f1 Mon Sep 17 00:00:00 2001 From: Leif Johansson Date: Wed, 9 Feb 2011 11:08:19 +0100 Subject: restructure 2 --- src/templates/meetingtools/__init__.py | 0 src/templates/meetingtools/ac/__init__.py | 12 + src/templates/meetingtools/ac/api.py | 87 +++++++ src/templates/meetingtools/apps/__init__.py | 0 src/templates/meetingtools/apps/auth/__init__.py | 0 src/templates/meetingtools/apps/auth/utils.py | 26 +++ src/templates/meetingtools/apps/auth/views.py | 85 +++++++ .../meetingtools/apps/cluster/__init__.py | 0 src/templates/meetingtools/apps/cluster/admin.py | 10 + src/templates/meetingtools/apps/cluster/models.py | 16 ++ src/templates/meetingtools/apps/room/__init__.py | 0 src/templates/meetingtools/apps/room/admin.py | 10 + src/templates/meetingtools/apps/room/forms.py | 33 +++ src/templates/meetingtools/apps/room/models.py | 30 +++ src/templates/meetingtools/apps/room/views.py | 253 +++++++++++++++++++++ .../meetingtools/apps/userprofile/__init__.py | 0 .../meetingtools/apps/userprofile/admin.py | 4 + .../meetingtools/apps/userprofile/models.py | 21 ++ src/templates/meetingtools/extensions/__init__.py | 0 .../extensions/templatetags/__init__.py | 0 .../extensions/templatetags/datehumanize.py | 35 +++ .../meetingtools/extensions/templatetags/prefix.py | 13 ++ .../extensions/templatetags/roomurl.py | 20 ++ src/templates/meetingtools/manage.py | 11 + src/templates/meetingtools/mimeparse.py | 123 ++++++++++ src/templates/meetingtools/multiresponse.py | 68 ++++++ src/templates/meetingtools/settings.py | 108 +++++++++ src/templates/meetingtools/site_logging.py | 10 + src/templates/meetingtools/urls.py | 35 +++ src/templates/meetingtools/utils.py | 17 ++ 30 files changed, 1027 insertions(+) create mode 100644 src/templates/meetingtools/__init__.py create mode 100644 src/templates/meetingtools/ac/__init__.py create mode 100644 src/templates/meetingtools/ac/api.py create mode 100644 src/templates/meetingtools/apps/__init__.py create mode 100644 src/templates/meetingtools/apps/auth/__init__.py create mode 100644 src/templates/meetingtools/apps/auth/utils.py create mode 100644 src/templates/meetingtools/apps/auth/views.py create mode 100644 src/templates/meetingtools/apps/cluster/__init__.py create mode 100644 src/templates/meetingtools/apps/cluster/admin.py create mode 100644 src/templates/meetingtools/apps/cluster/models.py create mode 100644 src/templates/meetingtools/apps/room/__init__.py create mode 100644 src/templates/meetingtools/apps/room/admin.py create mode 100644 src/templates/meetingtools/apps/room/forms.py create mode 100644 src/templates/meetingtools/apps/room/models.py create mode 100644 src/templates/meetingtools/apps/room/views.py create mode 100644 src/templates/meetingtools/apps/userprofile/__init__.py create mode 100644 src/templates/meetingtools/apps/userprofile/admin.py create mode 100644 src/templates/meetingtools/apps/userprofile/models.py create mode 100644 src/templates/meetingtools/extensions/__init__.py create mode 100644 src/templates/meetingtools/extensions/templatetags/__init__.py create mode 100644 src/templates/meetingtools/extensions/templatetags/datehumanize.py create mode 100644 src/templates/meetingtools/extensions/templatetags/prefix.py create mode 100644 src/templates/meetingtools/extensions/templatetags/roomurl.py create mode 100644 src/templates/meetingtools/manage.py create mode 100644 src/templates/meetingtools/mimeparse.py create mode 100644 src/templates/meetingtools/multiresponse.py create mode 100644 src/templates/meetingtools/settings.py create mode 100644 src/templates/meetingtools/site_logging.py create mode 100644 src/templates/meetingtools/urls.py create mode 100644 src/templates/meetingtools/utils.py (limited to 'src/templates/meetingtools') diff --git a/src/templates/meetingtools/__init__.py b/src/templates/meetingtools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/meetingtools/ac/__init__.py b/src/templates/meetingtools/ac/__init__.py new file mode 100644 index 0000000..1250a5f --- /dev/null +++ b/src/templates/meetingtools/ac/__init__.py @@ -0,0 +1,12 @@ +from meetingtools.ac.api import ACPClient + +def ac_api_client_cached(request,acc): + tag = 'ac_api_client_%s' % acc.name + if not request.session.has_key(tag): + request.session[tag] = ACPClient(acc.api_url,acc.user,acc.password) + + return request.session[tag] + +def ac_api_client(request,acc): + return ACPClient(acc.api_url,acc.user,acc.password) + \ No newline at end of file diff --git a/src/templates/meetingtools/ac/api.py b/src/templates/meetingtools/ac/api.py new file mode 100644 index 0000000..1ed974b --- /dev/null +++ b/src/templates/meetingtools/ac/api.py @@ -0,0 +1,87 @@ +''' +Created on Jan 31, 2011 + +@author: leifj +''' + +from lxml import etree +import httplib2 +from urllib import quote +import logging +from pprint import pformat + +class ACPException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return etree.tostring(self.value) + +class ACPResult(): + + def __init__(self,content): + self.et = etree.fromstring(content) + self.status = self.et.find('status') + + def is_error(self): + return self.status.get('code') != 'ok' + + def exception(self): + raise ACPException,self.status + + def get_principal(self): + return self.et.find('principal') + +def _enc(v): + ev = v + if isinstance(ev,str) or isinstance(ev,unicode): + ev = ev.encode('iso-8859-1') + return ev + +class ACPClient(): + + def __init__(self,url,username=None,password=None): + self.url = url + self.session = None + if username and password: + self.login(username,password) + + def request(self,method,p={},raise_error=False): + url = self.url+"?"+"action=%s" % method + if self.session: + url = url + "&session=%s" % self.session + + u = [] + for (k,v) in p.items(): + if v: + kv = "%s=%s" % (k,quote(str(v))) + u.append(kv) + url = url + "&" + "&".join(u) + + h = httplib2.Http(".cache"); + logging.debug(url) + resp, content = h.request(url, "GET") + logging.debug(pformat(resp)) + logging.debug(pformat(content)) + if resp.status != 200: + raise ACPException,resp.reason + + if resp.has_key('set-cookie'): + cookie = resp['set-cookie'] + if cookie: + avp = cookie.split(";") + if len(avp) > 0: + av = avp[0].split('=') + self.session = av[1] + + r = ACPResult(content) + if r.is_error() and raise_error: + raise r.exception() + + return r; + + def login(self,username,password): + result = self.request('login',{'login':username,'password':password}) + if result.is_error(): + raise result.exception() + \ No newline at end of file diff --git a/src/templates/meetingtools/apps/__init__.py b/src/templates/meetingtools/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/meetingtools/apps/auth/__init__.py b/src/templates/meetingtools/apps/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/meetingtools/apps/auth/utils.py b/src/templates/meetingtools/apps/auth/utils.py new file mode 100644 index 0000000..3a7efe6 --- /dev/null +++ b/src/templates/meetingtools/apps/auth/utils.py @@ -0,0 +1,26 @@ +''' +Created on Jul 7, 2010 + +@author: leifj +''' +from uuid import uuid4 + +def nonce(): + return uuid4().hex + +def anonid(): + return uuid4().urn + +def groups(request): + groups = [] + if request.user.is_authenticated(): + if request.session and request.session.has_key('entitlement'): + groups = groups + request.session['entitlement'] + + if '@' in request.user.username: + (local,domain) = request.user.username.split('@') + groups.append(domain) + for e in ('member','employee','student'): + groups.append("%s@%s" % (e,domain)) + + return groups \ No newline at end of file diff --git a/src/templates/meetingtools/apps/auth/views.py b/src/templates/meetingtools/apps/auth/views.py new file mode 100644 index 0000000..6828ac2 --- /dev/null +++ b/src/templates/meetingtools/apps/auth/views.py @@ -0,0 +1,85 @@ +''' +Created on Jul 5, 2010 + +@author: leifj +''' +from django.http import HttpResponseRedirect +from django.contrib.auth.models import User +import datetime +from django.views.decorators.cache import never_cache +import logging +from meetingtools.apps.userprofile.models import UserProfile +from meetingtools.multiresponse import redirect_to + +def meta(request,attr): + v = request.META.get(attr) + if not v: + return None + values = filter(lambda x: x != "(null)",v.split(";")) + return values; + +def meta1(request,attr): + v = meta(request,attr) + if v: + return v[0] + else: + return None + +def accounts_login_federated(request): + if request.user.is_authenticated(): + profile,created = UserProfile.objects.get_or_create(user=request.user) + if created: + profile.identifier = request.user.username + profile.user = request.user + profile.save() + + update = False + cn = meta1(request,'cn') + if not cn: + cn = meta1(request,'displayName') + logging.warn(cn) + if not cn: + fn = meta1(request,'givenName') + ln = meta1(request,'sn') + if fn and ln: + cn = "%s %s" % (fn,ln) + if not cn: + cn = profile.identifier + + mail = meta1(request,'mail') + + idp = meta1(request,'Shib-Identity-Provider') + + for attrib_name, meta_value in (('display_name',cn),('email',mail),('idp',idp)): + attrib_value = getattr(profile, attrib_name) + if meta_value and not attrib_value: + setattr(profile,attrib_name,meta_value) + update = True + + if request.user.password == "": + request.user.password = "(not used for federated logins)" + update = True + + if update: + request.user.save() + + # Allow auto_now to kick in for the lastupdated field + #profile.lastupdated = datetime.datetime.now() + profile.save() + + epe = meta(request,'entitlement') + if epe: + request.session['entitlement'] = epe + + next = request.session.get("after_login_redirect", None) + if next is not None: + return redirect_to(next) + else: + pass + return redirect_to("/") + +@never_cache +def logout(request): + from django.contrib.auth import logout + logout(request) + return HttpResponseRedirect("/Shibboleth.sso/Logout") \ No newline at end of file diff --git a/src/templates/meetingtools/apps/cluster/__init__.py b/src/templates/meetingtools/apps/cluster/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/meetingtools/apps/cluster/admin.py b/src/templates/meetingtools/apps/cluster/admin.py new file mode 100644 index 0000000..3fc9eea --- /dev/null +++ b/src/templates/meetingtools/apps/cluster/admin.py @@ -0,0 +1,10 @@ +''' +Created on Jan 31, 2011 + +@author: leifj +''' + +from django.contrib import admin +from meetingtools.apps.cluster.models import ACCluster + +admin.site.register(ACCluster) \ No newline at end of file diff --git a/src/templates/meetingtools/apps/cluster/models.py b/src/templates/meetingtools/apps/cluster/models.py new file mode 100644 index 0000000..9748073 --- /dev/null +++ b/src/templates/meetingtools/apps/cluster/models.py @@ -0,0 +1,16 @@ +''' +Created on Feb 3, 2011 + +@author: leifj +''' + +from django.db import models +from django.db.models.fields import CharField, URLField, TextField + +class ACCluster(models.Model): + api_url = URLField() + url = URLField() + user = CharField(max_length=128) + password = CharField(max_length=128) + name = CharField(max_length=128,blank=True,unique=True) + domain_match = TextField() \ No newline at end of file diff --git a/src/templates/meetingtools/apps/room/__init__.py b/src/templates/meetingtools/apps/room/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/meetingtools/apps/room/admin.py b/src/templates/meetingtools/apps/room/admin.py new file mode 100644 index 0000000..13d80a8 --- /dev/null +++ b/src/templates/meetingtools/apps/room/admin.py @@ -0,0 +1,10 @@ +''' +Created on Jan 31, 2011 + +@author: leifj +''' + +from django.contrib import admin +from meetingtools.apps.room.models import Room + +admin.site.register(Room) \ No newline at end of file diff --git a/src/templates/meetingtools/apps/room/forms.py b/src/templates/meetingtools/apps/room/forms.py new file mode 100644 index 0000000..3b89297 --- /dev/null +++ b/src/templates/meetingtools/apps/room/forms.py @@ -0,0 +1,33 @@ +''' +Created on Feb 1, 2011 + +@author: leifj +''' +from django.forms.models import ModelForm +from meetingtools.apps.room.models import Room +from django.forms.widgets import Select, TextInput +from django.forms.fields import ChoiceField, BooleanField +from django.forms.forms import Form + +PUBLIC = 0 +PROTECTED = 1 +PRIVATE = 2 + +class UpdateRoomForm(ModelForm): + #protection = ChoiceField(choices=((PUBLIC,'Anyone can enter the room.'), + # (PROTECTED,'Only group members and accepted guests can enter the room.'), + # (PRIVATE,'Only group members can enter.'))) + + class Meta: + model = Room + fields = ['name','urlpath','participants','presenters','hosts','source_sco_id','self_cleaning'] + widgets = {'participants': Select(), + 'presenters': Select(), + 'hosts': Select(), + 'source_sco_id': Select(), + 'urlpath': TextInput(attrs={'size': '40'}), + 'name': TextInput(attrs={'size': '40'}), + } + +class DeleteRoomForm(Form): + confirm = BooleanField(label="Confirm remove room") \ No newline at end of file diff --git a/src/templates/meetingtools/apps/room/models.py b/src/templates/meetingtools/apps/room/models.py new file mode 100644 index 0000000..90e5868 --- /dev/null +++ b/src/templates/meetingtools/apps/room/models.py @@ -0,0 +1,30 @@ +''' +Created on Jan 31, 2011 + +@author: leifj +''' + +from django.db import models +from django.db.models.fields import CharField, BooleanField, IntegerField, SmallIntegerField +from django.db.models.fields.related import ForeignKey +from django.contrib.auth.models import User +from meetingtools.apps.cluster.models import ACCluster + +class Room(models.Model): + creator = ForeignKey(User,editable=False) + name = CharField(max_length=128,blank=True,unique=True) + urlpath = CharField(max_length=128,blank=True,unique=True) + acc = ForeignKey(ACCluster,verbose_name="Adobe Connect Cluster",editable=False) + participants = CharField(max_length=255,blank=True,verbose_name="Participants") # populate from entitlement held by creator session + presenters = CharField(max_length=255,blank=True,verbose_name="Presenters") # populate from entitlement held by creator session + hosts = CharField(max_length=255,blank=True,verbose_name="Hosts") # populate from entitlement held by creator session + self_cleaning = BooleanField(verbose_name="Clean-up when empty?") + sco_id = IntegerField(verbose_name="Adobe Connect Room",blank=False) + source_sco_id = IntegerField(verbose_name="Template",blank=True,null=True) + timecreated = models.DateTimeField(auto_now_add=True) + lastupdated = models.DateTimeField(auto_now=True) + lastvisited = models.DateTimeField(blank=True,null=True) + + def __unicode__(self): + return "%s (sco_id=%d,source_sco_id=%d)" % (self.name,self.sco_id,self.source_sco_id) + \ No newline at end of file diff --git a/src/templates/meetingtools/apps/room/views.py b/src/templates/meetingtools/apps/room/views.py new file mode 100644 index 0000000..7bdffc0 --- /dev/null +++ b/src/templates/meetingtools/apps/room/views.py @@ -0,0 +1,253 @@ +''' +Created on Jan 31, 2011 + +@author: leifj +''' +from meetingtools.apps.room.models import Room, ACCluster +from meetingtools.multiresponse import respond_to, redirect_to +from meetingtools.apps.room.forms import UpdateRoomForm, DeleteRoomForm +from django.shortcuts import get_object_or_404 +from meetingtools.ac import ac_api_client, api +import re +from meetingtools.apps import room +from django.contrib.auth.decorators import login_required +from meetingtools.apps.auth.utils import groups +import logging +from pprint import pformat +from meetingtools.utils import session +import time +from meetingtools.settings import GRACE +from django.utils.datetime_safe import datetime + +def _acc_for_user(user): + (local,domain) = user.username.split('@') + if not domain: + #raise Exception,"Improperly formatted user: %s" % user.username + domain = "nordu.net" # testing with local accts only + for acc in ACCluster.objects.all(): + for regex in acc.domain_match.split(): + if re.match(regex,domain): + return acc + raise Exception,"I don't know which cluster you belong to... (%s)" % user.username + +def _user_meeting_folder(request,acc): + if not session(request,'my_meetings_sco_id'): + connect_api = ac_api_client(request, acc) + userid = request.user.username + folders = connect_api.request('sco-search-by-field',{'filter-type': 'folder','field':'name','query':userid}).et.xpath('//sco[folder-name="User Meetings"]') + logging.debug("user meetings folder: "+pformat(folders)) + #folder = next((f for f in folders if f.findtext('.//folder-name') == 'User Meetings'), None) + if folders and len(folders) > 0: + session(request,'my_meetings_sco_id',folders[0].get('sco-id')) + return session(request,'my_meetings_sco_id') + +def _shared_templates_folder(request,acc): + if not session(request,'shared_templates_sco_id'): + connect_api = ac_api_client(request, acc) + shared = connect_api.request('sco-shortcuts').et.xpath('.//sco[@type="shared-meeting-templates"]') + logging.debug("shared templates folder: "+pformat(shared)) + #folder = next((f for f in folders if f.findtext('.//folder-name') == 'User Meetings'), None) + if shared and len(shared) > 0: + session(request,'shared_templates_sco_id',shared[0].get('sco-id')) + return session(request,'shared_templates_sco_id') + +def _user_rooms(request,acc,my_meetings_sco_id): + rooms = [] + if my_meetings_sco_id: + connect_api = ac_api_client(request, acc) + meetings = connect_api.request('sco-expanded-contents',{'sco-id': my_meetings_sco_id,'filter-type': 'meeting'}) + if meetings: + rooms = [(r.get('sco-id'),r.findtext('name'),r.get('source-sco-id'),r.findtext('url-path')) for r in meetings.et.findall('.//sco')] + return rooms + +def _user_templates(request,acc,my_meetings_sco_id): + templates = [] + connect_api = ac_api_client(request, acc) + if my_meetings_sco_id: + my_templates = connect_api.request('sco-contents',{'sco-id': my_meetings_sco_id,'filter-type': 'folder'}).et.xpath('.//sco[folder-name="My Templates"][0]') + if my_templates and len(my_templates) > 0: + my_templates_sco_id = my_templates[0].get('sco_id') + meetings = connect_api.request('sco-contents',{'sco-id': my_templates_sco_id,'filter-type': 'meeting'}) + if meetings: + templates = templates + [(r.get('sco-id'),r.findtext('name')) for r in meetings.et.findall('.//sco')] + + shared_templates_sco_id = _shared_templates_folder(request, acc) + if shared_templates_sco_id: + shared_templates = connect_api.request('sco-contents',{'sco-id': shared_templates_sco_id,'filter-type': 'meeting'}) + if shared_templates: + templates = templates + [(r.get('sco-id'),r.findtext('name')) for r in shared_templates.et.findall('.//sco')] + + return templates + +def _find_current_session(session_info): + for r in session_info.et.xpath('//row'): + #logging.debug(pformat(etree.tostring(r))) + end = r.findtext('date-end') + if end is None: + return r + return None + +def _nusers(session_info): + cur = _find_current_session(session_info) + if cur is not None: + return cur.get('num-participants') + else: + return 0 + +@login_required +def view(request,id): + room = get_object_or_404(Room,pk=id) + api = ac_api_client(request,room.acc) + room_info = api.request('sco-info',{'sco-id':room.sco_id},raise_error=True) + perm_info = api.request('permissions-info',{'acl-id':room.sco_id,'filter-principal-id': 'public-access'},raise_error=True) + session_info = api.request('report-meeting-sessions',{'sco-id':room.sco_id},raise_error=True) + + room.name = room_info.et.findtext('.//sco/name') + room.save() + return respond_to(request, + {'text/html':'apps/room/view.html'}, + {'user':request.user, + 'room':room, + 'permission': perm_info.et.find('.//principal').get('permission-id'), + 'nusers': _nusers(session_info) + }) + +def _init_update_form(request,form,acc,my_meetings_sco_id): + form.fields['participants'].widget.choices = [('','-- anyone --')]+[(g,g) for g in groups(request)] + form.fields['presenters'].widget.choices = [('','-- nobody --')]+[(g,g) for g in groups(request)] + form.fields['hosts'].widget.choices = [('','-- nobody --')]+[(g,g) for g in groups(request)] + form.fields['source_sco_id'].widget.choices = [('','-- select template --')]+[r for r in _user_templates(request,acc,my_meetings_sco_id)] + +@login_required +def update(request,id=None): + if id: + room = get_object_or_404(Room,pk=id) + acc = room.acc + what = "Update" + title = "Modify %s" % room.name + update = True + else: + acc = _acc_for_user(request.user) + room = Room(creator=request.user,acc=acc) + what = "Create" + title = "Create a new room" + update = False + + my_meetings_sco_id = _user_meeting_folder(request,acc) + + if request.method == 'POST': + form = UpdateRoomForm(request.POST,instance=room) + _init_update_form(request, form, acc, my_meetings_sco_id) + if form.is_valid(): + api = ac_api_client(request,acc) + params = {'type':'meeting','name':room.name,'folder-id':my_meetings_sco_id,'sco-id':room.sco_id,'source-sco-id':room.source_sco_id,'url-path':room.urlpath} + if form.cleaned_data.has_key('source_sco_id'): + params['source-sco-id'] = form.cleaned_data['source_sco_id'] + + if form.cleaned_data.has_key('urlpath'): + params['url-path'] = form.cleaned_data['urlpath'] + + r = api.request('sco-update',params,raise_error=True) + params['sco-id'] = r.et.find(".//sco").get('sco-id') + params['sco-source-id'] = r.et.find(".//sco").get('sco-source-id') + room = form.save() + room = _import_room(params['sco-id'],params['name'],params['source-sco-id'],params['url-path'],request.user,acc) + return redirect_to("/rooms#%d" % room.id) + else: + form = UpdateRoomForm(instance=room) + _init_update_form(request, form, acc, my_meetings_sco_id) + if update: + form.fields['urlpath'].widget.attrs['readonly'] = True + + return respond_to(request,{'text/html':'edit.html'},{'form':form,'formtitle': title,'submitname':'%s Room' % what}) + +def _import_room(sco_id,name,source_sco_id,urlpath,user,acc): + modified = False + room,created = Room.objects.get_or_create(sco_id=sco_id,acc=acc,creator=user) + + if room.name != name: + room.name = name + modified = True + + if not room.sco_id and sco_id: + room.sco_id = sco_id + modified = True + + if not room.source_sco_id and source_sco_id: + room.source_sco_id = source_sco_id + modified = True + + if room.urlpath != urlpath: + room.urlpath = urlpath.strip('/') + modified = True + + if '/' in room.urlpath: + room.urlpath = urlpath.strip('/') + modified = True + + if modified: + room.save() + + return room + +@login_required +def list(request): + acc = _acc_for_user(request.user) + my_meetings_sco_id = _user_meeting_folder(request,acc) + user_rooms = _user_rooms(request,acc,my_meetings_sco_id) + + ar = [] + for (sco_id,name,source_sco_id,urlpath) in user_rooms: + room = _import_room(sco_id,name,source_sco_id,urlpath,request.user,acc) + ar.append(int(sco_id)) + + #logging.debug(pformat(ar)) + + for r in Room.objects.filter(creator=request.user).all(): + #logging.debug(pformat(r)) + if (not r.sco_id in ar) and (not r.self_cleaning): + r.delete() + return respond_to(request,{'text/html':'apps/room/list.html'},{'user':request.user,'rooms':Room.objects.filter(creator=request.user).all()}) + +@login_required +def delete(request,id): + room = get_object_or_404(Room,pk=id) + if request.method == 'POST': + form = DeleteRoomForm(request.POST) + if form.is_valid(): + api = ac_api_client(request,room.acc) + api.request('sco-delete',{'sco-id':room.sco_id},raise_error=True) + room.delete() + return redirect_to("/rooms") + else: + form = DeleteRoomForm() + + return respond_to(request,{'text/html':'edit.html'},{'form':form,'formtitle': 'Delete %s' % room.name,'submitname':'Delete Room'}) + +def _clean(room): + pass + +def go_by_id(request,id): + room = get_object_or_404(Room,pk=id) + return goto(request,room) + +def go_by_path(request,path): + room = get_object_or_404(Room,urlpath=path) + return goto(request,room) + +def goto(request,room): + client = ac_api_client(request, room.acc) + session_info = client.request('report-meeting-sessions',{'sco-id':room.sco_id}) + + now = time.time() + if room.self_cleaning: + if (_nusers(session_info) == 0) and (abs(room.lastvisited - now) > GRACE): + _clean(room) + + room.lastvisited = datetime.now() + room.save() + + r = client.request('sco-info',{'sco-id':room.sco_id}) + urlpath = r.et.findtext('.//sco/url-path') + return redirect_to(room.acc.url+urlpath) + \ No newline at end of file diff --git a/src/templates/meetingtools/apps/userprofile/__init__.py b/src/templates/meetingtools/apps/userprofile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/meetingtools/apps/userprofile/admin.py b/src/templates/meetingtools/apps/userprofile/admin.py new file mode 100644 index 0000000..21ca598 --- /dev/null +++ b/src/templates/meetingtools/apps/userprofile/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from meetingtools.apps.userprofile.models import UserProfile + +admin.site.register(UserProfile) \ No newline at end of file diff --git a/src/templates/meetingtools/apps/userprofile/models.py b/src/templates/meetingtools/apps/userprofile/models.py new file mode 100644 index 0000000..b0bc7ae --- /dev/null +++ b/src/templates/meetingtools/apps/userprofile/models.py @@ -0,0 +1,21 @@ +''' +Created on Jul 5, 2010 + +@author: leifj +''' +from django.db import models +from django.contrib.auth.models import User + +class UserProfile(models.Model): + user = models.ForeignKey(User,blank=True,related_name='profile') + display_name = models.CharField(max_length=255,blank=True) + email = models.EmailField(blank=True) + idp = models.CharField(max_length=255) + timecreated = models.DateTimeField(auto_now_add=True) + lastupdated = models.DateTimeField(auto_now=True) + + def __unicode__(self): + return "%s - %s" % (self.user.username,self.display_name) + +def profile(user): + return UserProfile.objects.get(user=user) diff --git a/src/templates/meetingtools/extensions/__init__.py b/src/templates/meetingtools/extensions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/meetingtools/extensions/templatetags/__init__.py b/src/templates/meetingtools/extensions/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/templates/meetingtools/extensions/templatetags/datehumanize.py b/src/templates/meetingtools/extensions/templatetags/datehumanize.py new file mode 100644 index 0000000..9570612 --- /dev/null +++ b/src/templates/meetingtools/extensions/templatetags/datehumanize.py @@ -0,0 +1,35 @@ +from django import template +from django.template import defaultfilters + +register = template.Library() + +MOMENT = 120 # duration in seconds within which the time difference + # will be rendered as 'a moment ago' + +def datehumanize(value): + """ + Finds the difference between the datetime value given and now() + and returns appropriate humanize form + """ + + from datetime import datetime + + if isinstance(value, datetime): + delta = datetime.now() - value + if delta.days > 6: + return value.strftime("on %b %d") # May 15 + if delta.days > 1: + return value.strftime("on %A") # Wednesday + elif delta.days == 1: + return 'yesterday' # yesterday + elif delta.seconds > 3600: + return str(delta.seconds / 3600 ) + ' hours ago' # 3 hours ago + elif delta.seconds > MOMENT: + return str(delta.seconds/60) + ' minutes ago' # 29 minutes ago + else: + return 'a moment ago' # a moment ago + return defaultfilters.date(value) + else: + return str(value) +datehumanize.is_safe = True +register.filter(datehumanize) \ No newline at end of file diff --git a/src/templates/meetingtools/extensions/templatetags/prefix.py b/src/templates/meetingtools/extensions/templatetags/prefix.py new file mode 100644 index 0000000..4defd38 --- /dev/null +++ b/src/templates/meetingtools/extensions/templatetags/prefix.py @@ -0,0 +1,13 @@ +''' +Created on Feb 9, 2011 + +@author: leifj +''' + +from django import template +from meetingtools.settings import PREFIX_URL +register = template.Library() + +@register.simple_tag +def prefix(): + return PREFIX_URL \ No newline at end of file diff --git a/src/templates/meetingtools/extensions/templatetags/roomurl.py b/src/templates/meetingtools/extensions/templatetags/roomurl.py new file mode 100644 index 0000000..37f1e80 --- /dev/null +++ b/src/templates/meetingtools/extensions/templatetags/roomurl.py @@ -0,0 +1,20 @@ +from django import template +from meetingtools.settings import BASE_URL + +register = template.Library() + +MOMENT = 120 # duration in seconds within which the time difference + # will be rendered as 'a moment ago' + +def roomurl(room): + """ + Display the public 'go' URL of a meetingroom + """ + path = room.id + if room.urlpath: + path = room.urlpath + + return "%s/go/%s" % (BASE_URL,path) + +roomurl.is_safe = True +register.filter(roomurl) \ No newline at end of file diff --git a/src/templates/meetingtools/manage.py b/src/templates/meetingtools/manage.py new file mode 100644 index 0000000..5e78ea9 --- /dev/null +++ b/src/templates/meetingtools/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/src/templates/meetingtools/mimeparse.py b/src/templates/meetingtools/mimeparse.py new file mode 100644 index 0000000..0fd91e7 --- /dev/null +++ b/src/templates/meetingtools/mimeparse.py @@ -0,0 +1,123 @@ +"""MIME-Type Parser + +This module provides basic functions for handling mime-types. It can handle +matching mime-types against a list of media-ranges. See section 14.1 of +the HTTP specification [RFC 2616] for a complete explanation. + + http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 + +Contents: + - parse_mime_type(): Parses a mime-type into its component parts. + - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' quality parameter. + - quality(): Determines the quality ('q') of a mime-type when compared against a list of media-ranges. + - quality_parsed(): Just like quality() except the second parameter must be pre-parsed. + - best_match(): Choose the mime-type with the highest quality ('q') from a list of candidates. +""" + +__version__ = "0.1.2" +__author__ = 'Joe Gregorio' +__email__ = "joe@bitworking.org" +__credits__ = "" + +def parse_mime_type(mime_type): + """Carves up a mime-type and returns a tuple of the + (type, subtype, params) where 'params' is a dictionary + of all the parameters for the media range. + For example, the media range 'application/xhtml;q=0.5' would + get parsed into: + + ('application', 'xhtml', {'q', '0.5'}) + """ + parts = mime_type.split(";") + params = dict([tuple([s.strip() for s in param.split("=")])\ + for param in parts[1:] ]) + full_type = parts[0].strip() + # Java URLConnection class sends an Accept header that includes a single "*" + # Turn it into a legal wildcard. + if full_type == '*': full_type = '*/*' + (type, subtype) = full_type.split("/") + return (type.strip(), subtype.strip(), params) + +def parse_media_range(range): + """Carves up a media range and returns a tuple of the + (type, subtype, params) where 'params' is a dictionary + of all the parameters for the media range. + For example, the media range 'application/*;q=0.5' would + get parsed into: + + ('application', '*', {'q', '0.5'}) + + In addition this function also guarantees that there + is a value for 'q' in the params dictionary, filling it + in with a proper default if necessary. + """ + (type, subtype, params) = parse_mime_type(range) + if not params.has_key('q') or not params['q'] or \ + not float(params['q']) or float(params['q']) > 1\ + or float(params['q']) < 0: + params['q'] = '1' + return (type, subtype, params) + +def fitness_and_quality_parsed(mime_type, parsed_ranges): + """Find the best match for a given mime-type against + a list of media_ranges that have already been + parsed by parse_media_range(). Returns a tuple of + the fitness value and the value of the 'q' quality + parameter of the best match, or (-1, 0) if no match + was found. Just as for quality_parsed(), 'parsed_ranges' + must be a list of parsed media ranges. """ + best_fitness = -1 + best_fit_q = 0 + (target_type, target_subtype, target_params) =\ + parse_media_range(mime_type) + for (type, subtype, params) in parsed_ranges: + if (type == target_type or type == '*' or target_type == '*') and \ + (subtype == target_subtype or subtype == '*' or target_subtype == '*'): + param_matches = reduce(lambda x, y: x+y, [1 for (key, value) in \ + target_params.iteritems() if key != 'q' and \ + params.has_key(key) and value == params[key]], 0) + fitness = (type == target_type) and 100 or 0 + fitness += (subtype == target_subtype) and 10 or 0 + fitness += param_matches + if fitness > best_fitness: + best_fitness = fitness + best_fit_q = params['q'] + + return best_fitness, float(best_fit_q) + +def quality_parsed(mime_type, parsed_ranges): + """Find the best match for a given mime-type against + a list of media_ranges that have already been + parsed by parse_media_range(). Returns the + 'q' quality parameter of the best match, 0 if no + match was found. This function bahaves the same as quality() + except that 'parsed_ranges' must be a list of + parsed media ranges. """ + return fitness_and_quality_parsed(mime_type, parsed_ranges)[1] + +def quality(mime_type, ranges): + """Returns the quality 'q' of a mime-type when compared + against the media-ranges in ranges. For example: + + >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5') + 0.7 + + """ + parsed_ranges = [parse_media_range(r) for r in ranges.split(",")] + return quality_parsed(mime_type, parsed_ranges) + +def best_match(supported, header): + """Takes a list of supported mime-types and finds the best + match for all the media-ranges listed in header. The value of + header must be a string that conforms to the format of the + HTTP Accept: header. The value of 'supported' is a list of + mime-types. + + >>> best_match(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1') + 'text/xml' + """ + parsed_header = [parse_media_range(r) for r in header.split(",")] + weighted_matches = [(fitness_and_quality_parsed(mime_type, parsed_header), mime_type)\ + for mime_type in supported] + weighted_matches.sort() + return weighted_matches[-1][0][1] and weighted_matches[-1][1] or '' diff --git a/src/templates/meetingtools/multiresponse.py b/src/templates/meetingtools/multiresponse.py new file mode 100644 index 0000000..3427a82 --- /dev/null +++ b/src/templates/meetingtools/multiresponse.py @@ -0,0 +1,68 @@ +import meetingtools.mimeparse as mimeparse +import re +import rfc822 +from django.conf import settings +from django.shortcuts import render_to_response +from django.http import HttpResponse, HttpResponseForbidden,\ + HttpResponseRedirect +from django.utils import simplejson +from django.template import loader +from meetingtools.settings import PREFIX_URL + +default_suffix_mapping = {"\.htm(l?)$": "text/html", + "\.json$": "application/json", + "\.rss$": "application/rss+xml", + "\.torrent$": "application/x-bittorrent"} + +def _accept_types(request, suffix): + for r in suffix.keys(): + p = re.compile(r) + if p.search(request.path): + return suffix.get(r) + return None + + +def timeAsrfc822 ( theTime ) : + return rfc822 . formatdate ( rfc822 . mktime_tz ( rfc822 . parsedate_tz ( theTime . strftime ( "%a, %d %b %Y %H:%M:%S" ) ) ) ) + +def make_response_dict(request,d={}): + + if request.user.is_authenticated(): + d['user'] = request.user + d['prefix'] = PREFIX_URL + + return d + +def json_response(data): + r = HttpResponse(simplejson.dumps(data),content_type='application/json') + r['Cache-Control'] = 'no-cache, must-revalidate' + r['Pragma'] = 'no-cache' + + return r + +def render403(message="You don't seem to have enough rights for what you are trying to do....",dict={}): + dict['message'] = message + return HttpResponseForbidden(loader.render_to_string("403.html",dict)) + +def respond_to(request, template_mapping, dict={}, suffix_mapping=default_suffix_mapping): + accept = _accept_types(request, suffix_mapping) + if accept is None: + accept = (request.META['HTTP_ACCEPT'].split(','))[0] + content_type = mimeparse.best_match(template_mapping.keys(), accept) + template = None + if template_mapping.has_key(content_type): + template = template_mapping[content_type] + else: + template = template_mapping["text/html"] + if callable(template): + response = template(make_response_dict(request,dict)) + elif isinstance(template, HttpResponse): + response = template + response['Content-Type'] = "%s; charset=%s" % (content_type, settings.DEFAULT_CHARSET) + else: + response = render_to_response(template,make_response_dict(request,dict)) + response['Content-Type'] = "%s; charset=%s" % (content_type, settings.DEFAULT_CHARSET) + return response + +def redirect_to(path): + return HttpResponseRedirect("%s%s" % (PREFIX_URL,path)) \ No newline at end of file diff --git a/src/templates/meetingtools/settings.py b/src/templates/meetingtools/settings.py new file mode 100644 index 0000000..e46ca8d --- /dev/null +++ b/src/templates/meetingtools/settings.py @@ -0,0 +1,108 @@ +# Django settings for meetingtools project. + +import meetingtools.site_logging +import os + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +BASE_DIR = '.' + +PREFIX_URL = "" +BASE_URL = "http://localhost:8000%s" % PREFIX_URL +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': '%s/db/sqlite.db' % BASE_DIR, # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +GRACE = 10 + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'Europe/Stockholm' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale +USE_L10N = True + +MEDIA_ROOT = "%s/site-media" % BASE_DIR +ADMIN_MEDIA_ROOT = "%s/admin-media" % BASE_DIR +MEDIA_URL = '%s/site-media/' % PREFIX_URL +ADMIN_MEDIA_PREFIX = '%s/admin-media/' % PREFIX_URL + + +LOGIN_URL = "%s/accounts/login/" % PREFIX_URL +LOGOUT_URL = "%s/accounts/logout/" % PREFIX_URL + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'tz78l!c=cl2=jic5$2#(bq)7-4s1ivtm*a+q0w1yi0$)hrmc7l' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.RemoteUserMiddleware' +) + +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.RemoteUserBackend', + 'django.contrib.auth.backends.ModelBackend', +) + +ROOT_URLCONF = 'meetingtools.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + "%s/templates" % BASE_DIR +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'django.contrib.humanize', + 'django_extensions', + 'meetingtools.extensions', + 'meetingtools.apps.auth', + 'meetingtools.apps.room', + 'meetingtools.apps.cluster', + 'meetingtools.apps.userprofile', +) diff --git a/src/templates/meetingtools/site_logging.py b/src/templates/meetingtools/site_logging.py new file mode 100644 index 0000000..cdbe3c2 --- /dev/null +++ b/src/templates/meetingtools/site_logging.py @@ -0,0 +1,10 @@ +import logging +import sys + +logger = logging.getLogger('') +logger.setLevel(logging.DEBUG) +handler = logging.StreamHandler(sys.stderr) +handler.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(levelname)-8s %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) diff --git a/src/templates/meetingtools/urls.py b/src/templates/meetingtools/urls.py new file mode 100644 index 0000000..785f90a --- /dev/null +++ b/src/templates/meetingtools/urls.py @@ -0,0 +1,35 @@ +from django.conf.urls.defaults import * + +# Uncomment the next two lines to enable the admin: +from django.contrib import admin +from django.http import HttpResponseRedirect +from django.contrib.auth.views import login, logout +from meetingtools.settings import ADMIN_MEDIA_ROOT, MEDIA_ROOT +from meetingtools.multiresponse import redirect_to +admin.autodiscover() + +def welcome(request): + return redirect_to('/rooms') + +urlpatterns = patterns('', + (r'^$',welcome), + (r'^admin-media/(?P.*)$', 'django.views.static.serve',{'document_root': ADMIN_MEDIA_ROOT}), + (r'^site-media/(?P.*)$', 'django.views.static.serve',{'document_root': MEDIA_ROOT}), + # Login/Logout + (r'^accounts/login/$',login,{'template_name': "login.html"}), + (r'^accounts/logout$',logout), + (r'^accounts/login-federated/$','meetingtools.apps.auth.views.accounts_login_federated'), + (r'^accounts/logout/$','meetingtools.apps.auth.views.logout'), + (r'^rooms$','meetingtools.apps.room.views.list'), + (r'^go/(\d+)$','meetingtools.apps.room.views.go_by_id'), + (r'^go/(.+)$','meetingtools.apps.room.views.go_by_path'), + (r'^room/create$','meetingtools.apps.room.views.update'), + (r'^room/(\d+)$','meetingtools.apps.room.views.view'), + (r'^room/(\d+)/modify$','meetingtools.apps.room.views.update'), + (r'^room/(\d+)/delete$','meetingtools.apps.room.views.delete'), + # Uncomment the admin/doc line below to enable admin documentation: + # (r'^admin/doc/', include('django.contrib.admindocs.urls')), + + # Uncomment the next line to enable the admin: + (r'^admin/', include(admin.site.urls)) +) diff --git a/src/templates/meetingtools/utils.py b/src/templates/meetingtools/utils.py new file mode 100644 index 0000000..cadfb36 --- /dev/null +++ b/src/templates/meetingtools/utils.py @@ -0,0 +1,17 @@ +''' +Created on Feb 4, 2011 + +@author: leifj +''' + +def session(request,key=None,val=None): + if key: + if val: + request.session[key] = val + return val + else: + if not request.session.has_key(key): + request.session[key] = None + return request.session[key] + else: + return request.session \ No newline at end of file -- cgit v1.1