summaryrefslogtreecommitdiff
path: root/meetingtools
diff options
context:
space:
mode:
authorLeif Johansson <leifj@sunet.se>2011-02-08 20:46:40 +0100
committerLeif Johansson <leifj@sunet.se>2011-02-08 20:46:40 +0100
commit0fb514f0a73aef806b826348d043c68534af2745 (patch)
treebd388ab8e3de4c93a6ba0a5e1b83a4087a14b6e2 /meetingtools
parenta20ff434501a099b259da903d88b47bb77e07469 (diff)
v1
Diffstat (limited to 'meetingtools')
-rw-r--r--meetingtools/ac/__init__.py12
-rw-r--r--meetingtools/ac/api.py37
-rw-r--r--meetingtools/apps/auth/__init__.py0
-rw-r--r--meetingtools/apps/auth/utils.py26
-rw-r--r--meetingtools/apps/auth/views.py84
-rw-r--r--meetingtools/apps/cluster/__init__.py0
-rw-r--r--meetingtools/apps/cluster/admin.py10
-rw-r--r--meetingtools/apps/cluster/models.py16
-rw-r--r--meetingtools/apps/room/forms.py33
-rw-r--r--meetingtools/apps/room/models.py26
-rw-r--r--meetingtools/apps/room/views.py254
-rw-r--r--meetingtools/apps/userprofile/__init__.py0
-rw-r--r--meetingtools/apps/userprofile/admin.py4
-rw-r--r--meetingtools/apps/userprofile/models.py21
-rw-r--r--meetingtools/extensions/templatetags/__init__.py0
-rw-r--r--meetingtools/extensions/templatetags/datehumanize.py35
-rw-r--r--meetingtools/extensions/templatetags/roomurl.py20
-rw-r--r--meetingtools/mimeparse.py123
-rw-r--r--meetingtools/multiresponse.py62
-rw-r--r--meetingtools/settings.py14
-rw-r--r--meetingtools/urls.py32
-rw-r--r--meetingtools/utils.py17
22 files changed, 803 insertions, 23 deletions
diff --git a/meetingtools/ac/__init__.py b/meetingtools/ac/__init__.py
index e69de29..1250a5f 100644
--- a/meetingtools/ac/__init__.py
+++ b/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/meetingtools/ac/api.py b/meetingtools/ac/api.py
index 2631987..1ed974b 100644
--- a/meetingtools/ac/api.py
+++ b/meetingtools/ac/api.py
@@ -6,19 +6,21 @@ Created on Jan 31, 2011
from lxml import etree
import httplib2
-from urllib import urlencode
+from urllib import quote
+import logging
+from pprint import pformat
class ACPException(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
- return repr(self.value)
+ return etree.tostring(self.value)
class ACPResult():
def __init__(self,content):
- self.et = etree.parse(content)
+ self.et = etree.fromstring(content)
self.status = self.et.find('status')
def is_error(self):
@@ -30,6 +32,12 @@ class ACPResult():
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):
@@ -37,15 +45,24 @@ class ACPClient():
self.session = None
if username and password:
self.login(username,password)
-
- def request(self,method,p={}):
+
+ def request(self,method,p={},raise_error=False):
url = self.url+"?"+"action=%s" % method
if self.session:
url = url + "&session=%s" % self.session
- urlencode(dict([k,v.encode("iso-8859-1")] for (k,v) in p.items()))
+
+ 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
@@ -53,11 +70,15 @@ class ACPClient():
cookie = resp['set-cookie']
if cookie:
avp = cookie.split(";")
- if avp.len > 0:
+ if len(avp) > 0:
av = avp[0].split('=')
self.session = av[1]
- return ACPResult(content)
+ 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})
diff --git a/meetingtools/apps/auth/__init__.py b/meetingtools/apps/auth/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/meetingtools/apps/auth/__init__.py
diff --git a/meetingtools/apps/auth/utils.py b/meetingtools/apps/auth/utils.py
new file mode 100644
index 0000000..3a7efe6
--- /dev/null
+++ b/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/meetingtools/apps/auth/views.py b/meetingtools/apps/auth/views.py
new file mode 100644
index 0000000..877e43f
--- /dev/null
+++ b/meetingtools/apps/auth/views.py
@@ -0,0 +1,84 @@
+'''
+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
+
+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 HttpResponseRedirect(next)
+ else:
+ pass
+ return HttpResponseRedirect("/")
+
+@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/meetingtools/apps/cluster/__init__.py b/meetingtools/apps/cluster/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/meetingtools/apps/cluster/__init__.py
diff --git a/meetingtools/apps/cluster/admin.py b/meetingtools/apps/cluster/admin.py
new file mode 100644
index 0000000..3fc9eea
--- /dev/null
+++ b/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/meetingtools/apps/cluster/models.py b/meetingtools/apps/cluster/models.py
new file mode 100644
index 0000000..9748073
--- /dev/null
+++ b/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/meetingtools/apps/room/forms.py b/meetingtools/apps/room/forms.py
new file mode 100644
index 0000000..3b89297
--- /dev/null
+++ b/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/meetingtools/apps/room/models.py b/meetingtools/apps/room/models.py
index f75478e..90e5868 100644
--- a/meetingtools/apps/room/models.py
+++ b/meetingtools/apps/room/models.py
@@ -5,10 +5,26 @@ Created on Jan 31, 2011
'''
from django.db import models
-from django.db.models.fields import CharField, BooleanField, URLField
+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):
- name = CharField(max_length=128)
- group = CharField(max_length=128) # populate from entitlement held by creator
- resetWhenEmpty = BooleanField()
- meetingRoomUrl = URLField() \ No newline at end of file
+ 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/meetingtools/apps/room/views.py b/meetingtools/apps/room/views.py
index e69de29..2667a70 100644
--- a/meetingtools/apps/room/views.py
+++ b/meetingtools/apps/room/views.py
@@ -0,0 +1,254 @@
+'''
+Created on Jan 31, 2011
+
+@author: leifj
+'''
+from meetingtools.apps.room.models import Room, ACCluster
+from django.http import HttpResponseRedirect
+from meetingtools.multiresponse import respond_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
+ return None
+
+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 HttpResponseRedirect("/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 HttpResponseRedirect("/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 HttpResponseRedirect(room.acc.url+urlpath)
+ \ No newline at end of file
diff --git a/meetingtools/apps/userprofile/__init__.py b/meetingtools/apps/userprofile/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/meetingtools/apps/userprofile/__init__.py
diff --git a/meetingtools/apps/userprofile/admin.py b/meetingtools/apps/userprofile/admin.py
new file mode 100644
index 0000000..21ca598
--- /dev/null
+++ b/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/meetingtools/apps/userprofile/models.py b/meetingtools/apps/userprofile/models.py
new file mode 100644
index 0000000..b0bc7ae
--- /dev/null
+++ b/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/meetingtools/extensions/templatetags/__init__.py b/meetingtools/extensions/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/meetingtools/extensions/templatetags/__init__.py
diff --git a/meetingtools/extensions/templatetags/datehumanize.py b/meetingtools/extensions/templatetags/datehumanize.py
new file mode 100644
index 0000000..9570612
--- /dev/null
+++ b/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/meetingtools/extensions/templatetags/roomurl.py b/meetingtools/extensions/templatetags/roomurl.py
new file mode 100644
index 0000000..37f1e80
--- /dev/null
+++ b/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/meetingtools/mimeparse.py b/meetingtools/mimeparse.py
new file mode 100644
index 0000000..0fd91e7
--- /dev/null
+++ b/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/meetingtools/multiresponse.py b/meetingtools/multiresponse.py
new file mode 100644
index 0000000..b8c0960
--- /dev/null
+++ b/meetingtools/multiresponse.py
@@ -0,0 +1,62 @@
+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
+from django.utils import simplejson
+from django.template import loader
+
+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
+
+ 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
diff --git a/meetingtools/settings.py b/meetingtools/settings.py
index 3c43a57..f40ff8c 100644
--- a/meetingtools/settings.py
+++ b/meetingtools/settings.py
@@ -1,6 +1,7 @@
# Django settings for meetingtools project.
import meetingtools.site_logging
+import os
DEBUG = True
TEMPLATE_DEBUG = DEBUG
@@ -9,7 +10,9 @@ ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)
-BASE_DIR = '..'
+BASE_DIR = '.'
+
+BASE_URL = "http://localhost:8000"
MANAGERS = ADMINS
@@ -24,6 +27,7 @@ DATABASES = {
}
}
+GRACE = 10
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
@@ -67,7 +71,6 @@ MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'coip.middleware.UserMappingMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware'
)
@@ -92,5 +95,10 @@ INSTALLED_APPS = (
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.humanize',
- 'django_extensions'
+ 'django_extensions',
+ 'meetingtools.extensions',
+ 'meetingtools.apps.auth',
+ 'meetingtools.apps.room',
+ 'meetingtools.apps.cluster',
+ 'meetingtools.apps.userprofile',
)
diff --git a/meetingtools/urls.py b/meetingtools/urls.py
index 69b4bf0..34aeb0a 100644
--- a/meetingtools/urls.py
+++ b/meetingtools/urls.py
@@ -1,16 +1,34 @@
from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin:
-# from django.contrib import admin
-# admin.autodiscover()
+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
+admin.autodiscover()
-urlpatterns = patterns('',
- # Example:
- # (r'^meetingtools/', include('meetingtools.foo.urls')),
+def welcome(request):
+ return HttpResponseRedirect('/rooms')
+urlpatterns = patterns('',
+ (r'^$',welcome),
+ (r'^admin-media/(?P<path>.*)$', 'django.views.static.serve',{'document_root': ADMIN_MEDIA_ROOT}),
+ (r'^site-media/(?P<path>.*)$', '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)),
+ (r'^admin/', include(admin.site.urls))
)
diff --git a/meetingtools/utils.py b/meetingtools/utils.py
new file mode 100644
index 0000000..cadfb36
--- /dev/null
+++ b/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