diff options
Diffstat (limited to 'meetingtools/apps/room/views.py')
-rw-r--r-- | meetingtools/apps/room/views.py | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/meetingtools/apps/room/views.py b/meetingtools/apps/room/views.py new file mode 100644 index 0000000..c46ab6e --- /dev/null +++ b/meetingtools/apps/room/views.py @@ -0,0 +1,548 @@ +''' +Created on Jan 31, 2011 + +@author: leifj +''' +from meetingtools.apps.room.models import Room, ACCluster +from meetingtools.multiresponse import respond_to, redirect_to, json_response +from meetingtools.apps.room.forms import DeleteRoomForm,\ + CreateRoomForm, ModifyRoomForm, TagRoomForm +from django.shortcuts import get_object_or_404 +from meetingtools.ac import ac_api_client +import re +from meetingtools.apps import room +from django.contrib.auth.decorators import login_required +import logging +from pprint import pformat +from meetingtools.utils import session, base_url +import time +from django.conf import settings +from django.utils.datetime_safe import datetime +from django.http import HttpResponseRedirect +from django.core.exceptions import ObjectDoesNotExist +from django_co_acls.models import allow, deny, acl, clear_acl +from meetingtools.ac.api import ACPClient +from tagging.models import Tag, TaggedItem +import random, string +from django.utils.feedgenerator import rfc3339_date +from django.views.decorators.cache import never_cache +from meetingtools.apps.cluster.models import acc_for_user +from django.contrib.auth.models import User +import iso8601 +from celery.execute import send_task +from meetingtools.apps.room.tasks import start_user_counts_poll + +def _user_meeting_folder(request,acc): + if not session(request,'my_meetings_sco_id'): + with ac_api_client(acc) as api: + userid = request.user.username + folders = 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'): + with ac_api_client(acc) as api: + shared = 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: + with ac_api_client(acc) as api: + meetings = api.request('sco-expanded-contents',{'sco-id': my_meetings_sco_id,'filter-type': 'meeting'}) + if meetings: + rooms = [{'sco_id': r.get('sco-id'), + 'name': r.findtext('name'), + 'source_sco_id': r.get('source-sco-id'), + 'urlpath': r.findtext('url-path'), + 'description': r.findtext('description')} for r in meetings.et.findall('.//sco')] + return rooms + +def _user_templates(request,acc,my_meetings_sco_id): + templates = [] + with ac_api_client(acc) as api: + if my_meetings_sco_id: + my_templates = 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 = 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 = 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) + return respond_to(request, + {'text/html':'apps/room/list.html'}, + {'user':request.user, + 'rooms':[room], + 'title': room.name, + 'baseurl': base_url(request), + 'active': True, + }) + +def _init_update_form(request,form,acc,my_meetings_sco_id): + if form.fields.has_key('urlpath'): + url = base_url(request) + form.fields['urlpath'].widget.prefix = url + if form.fields.has_key('source_sco_id'): + form.fields['source_sco_id'].widget.choices = [('','-- select template --')]+[r for r in _user_templates(request,acc,my_meetings_sco_id)] + +def _update_room(request, room, form=None): + params = {'type':'meeting'} + + for attr,param in (('sco_id','sco-id'),('folder_sco_id','folder-id'),('source_sco_id','source-sco-id'),('urlpath','url-path'),('name','name'),('description','description')): + v = None + if hasattr(room,attr): + v = getattr(room,attr) + logging.debug("%s,%s = %s" % (attr,param,v)) + if form and form.cleaned_data.has_key(attr) and form.cleaned_data[attr]: + v = form.cleaned_data[attr] + + if v: + if isinstance(v,(str,unicode)): + params[param] = v + elif hasattr(v,'__getitem__'): + params[param] = v[0] + else: + params[param] = repr(v) + + logging.debug(pformat(params)) + with ac_api_client(room.acc) as api: + r = api.request('sco-update', params, True) + sco_id = r.et.find(".//sco").get('sco-id') + if form: + form.cleaned_data['sco_id'] = sco_id + form.cleaned_data['source_sco_id'] = r.et.find(".//sco").get('sco-source-id') + + room.sco_id = sco_id + room.save() + + user_principal = api.find_user(room.creator.username) + #api.request('permissions-reset',{'acl-id': sco_id},True) + api.request('permissions-update',{'acl-id': sco_id,'principal-id': user_principal.get('principal-id'),'permission-id':'host'},True) # owner is always host + + if form: + if form.cleaned_data.has_key('access'): + access = form.cleaned_data['access'] + if access == 'public': + allow(room,'anyone','view-hidden') + elif access == 'private': + allow(room,'anyone','remove') + + # XXX figure out how to keep the room permissions in sync with the AC permissions + for ace in acl(room): + principal_id = None + if ace.group: + principal = api.find_group(ace.group.name) + if principal: + principal_id = principal.get('principal-id') + elif ace.user: + principal = api.find_user(ace.user.username) + if principal: + principal_id = principal.get('principal-id') + else: + principal_id = "public-access" + + if principal_id: + api.request('permissions-update',{'acl-id': room.sco_id, 'principal-id': principal_id, 'permission-id': ace.permission},True) + + room.deleted_sco_id = None # if we just cleaned a room we zero out the deleted_sco_id field to indicate the room is now ready for use + room.save() # a second save here to avoid races + return room + +@never_cache +@login_required +def create(request): + acc = acc_for_user(request.user) + my_meetings_sco_id = _user_meeting_folder(request,acc) + template_sco_id = acc.default_template_sco_id + if not template_sco_id: + template_sco_id = DEFAULT_TEMPLATE_SCO + room = Room(creator=request.user,acc=acc,folder_sco_id=my_meetings_sco_id,source_sco_id=template_sco_id) + what = "Create" + title = "Create a new room" + + if request.method == 'POST': + form = CreateRoomForm(request.POST,instance=room) + _init_update_form(request, form, acc, room.folder_sco_id) + if form.is_valid(): + _update_room(request, room, form) + room = form.save() + return redirect_to("/rooms#%d" % room.id) + else: + form = CreateRoomForm(instance=room) + _init_update_form(request, form, acc, room.folder_sco_id) + + return respond_to(request,{'text/html':'apps/room/create.html'},{'form':form,'formtitle': title,'cancelname':'Cancel','submitname':'%s Room' % what}) + +@never_cache +@login_required +def update(request,id): + room = get_object_or_404(Room,pk=id) + acc = room.acc + what = "Update" + title = "Modify %s" % room.name + + if request.method == 'POST': + form = ModifyRoomForm(request.POST,instance=room) + _init_update_form(request, form, acc, room.folder_sco_id) + if form.is_valid(): + _update_room(request, room, form) + room = form.save() + return redirect_to("/rooms#%d" % room.id) + else: + form = ModifyRoomForm(instance=room) + _init_update_form(request, form, acc, room.folder_sco_id) + + return respond_to(request,{'text/html':'apps/room/update.html'},{'form':form,'formtitle': title,'cancelname': 'Cancel','submitname':'%s Room' % what}) + +def _import_room(request,acc,r): + modified = False + room = None + + if room and (abs(room.lastupdate() - time.time()) < settings.IMPORT_TTL): + return room + + if r.has_key('urlpath'): + r['urlpath'] = r['urlpath'].strip('/') + + try: + room = Room.objects.get(sco_id=r['sco_id'],acc=acc) + for key in ('sco_id','name','source_sco_id','urlpath','description','user_count','host_count'): + if r.has_key(key) and hasattr(room,key): + rv = getattr(room,key) + if rv != r[key] and r[key] != None and r[key]: + setattr(room,key,r[key]) + modified = True + + if modified: + logging.debug("+++ saving room ... %s" % pformat(room)) + room.save() + + except ObjectDoesNotExist: + if r['folder_sco_id']: + try: + room = Room.objects.create(sco_id=r['sco_id'], + source_sco_id=r['source_sco_id'], + acc=acc, + name=r['name'], + urlpath=r['urlpath'], + description=r['description'], + creator=request.user, + folder_sco_id=r['folder_sco_id']) + except Exception,e: + room = None + pass + + if not room: + return None + + logging.debug("+++ looking at user counts") + with ac_api_client(acc) as api: + userlist = api.request('meeting-usermanager-user-list',{'sco-id': room.sco_id},False) + if userlist.status_code() == 'ok': + room.user_count = int(userlist.et.xpath("count(.//userdetails)")) + room.host_count = int(userlist.et.xpath("count(.//userdetails/role[text() = 'host'])")) + room.save() + + return room + +@login_required +def list_rooms(request,username=None): + user = request.user + if username: + try: + user = User.objects.get(username=username) + except ObjectDoesNotExist: + user = None + + rooms = [] + if user: + rooms = Room.objects.filter(creator=user).order_by('name').all() + + return respond_to(request, + {'text/html':'apps/room/list.html'}, + {'title':'Your Rooms','edit':True,'active':len(rooms) == 1,'rooms':rooms}) + +@login_required +def user_rooms(request,user=None): + if user is None: + user = request.user + + acc = acc_for_user(user) + my_meetings_sco_id = _user_meeting_folder(request,acc) + user_rooms = _user_rooms(request,acc,my_meetings_sco_id) + + ar = [] + for r in user_rooms: + logging.debug(pformat(r)) + ar.append(int(r['sco_id'])) + + for r in Room.objects.filter(creator=user).all(): + if (not r.sco_id in ar): # and (not r.self_cleaning): #XXX this logic isn't right! + for t in Tag.objects.get_for_object(r): + t.delete() + r.delete() + + for r in user_rooms: + r['folder_sco_id'] = my_meetings_sco_id + room = _import_room(request,acc,r) + + rooms = Room.objects.filter(creator=user).order_by('name').all() + return respond_to(request, + {'text/html':'apps/room/list.html'}, + {'title':'Your Rooms','edit':True,'active':len(rooms) == 1,'rooms':rooms}) + +@login_required +def unlock(request,id): + room = get_object_or_404(Room,pk=id) + room.unlock() + return redirect_to("/rooms#%d" % room.id) + +@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(): + with ac_api_client(room.acc) as api: + api.request('sco-delete',{'sco-id':room.sco_id},raise_error=True) + clear_acl(room) + room.delete() + + return redirect_to("/rooms") + else: + form = DeleteRoomForm() + + return respond_to(request,{'text/html':'edit.html'},{'form':form,'formtitle': 'Delete %s' % room.name,'cancelname':'Cancel','submitname':'Delete Room'}) + +def _clean(request,room): + with ac_api_client(room.acc) as api: + room.deleted_sco_id = room.sco_id + room.save() + api.request('sco-delete',{'sco-id':room.sco_id},raise_error=False) + room.sco_id = None + return _update_room(request, room) + +def occupation(request,rid): + room = get_object_or_404(Room,pk=rid) + with ac_api_client(room.acc) as api: + api.poll_user_counts(room) + d = {'nusers': room.user_count, 'nhosts': room.host_count} + return respond_to(request, + {'text/html': 'apps/room/fragments/occupation.txt', + 'application/json': json_response(d, request)}, + d) + +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) + +@login_required +def promote_and_launch(request,rid): + room = get_object_or_404(Room,pk=rid) + return _goto(request,room,clean=False,promote=True) + +def launch(request,rid): + room = get_object_or_404(Room,pk=rid) + return _goto(request,room,clean=False) + +def goto(request,room): + return _goto(request,room,clean=True) + +def _random_key(length=20): + rg = random.SystemRandom() + alphabet = string.letters + string.digits + return str().join(rg.choice(alphabet) for _ in range(length)) + +def _goto(request,room,clean=True,promote=False): + if room.is_locked(): + return respond_to(request, {"text/html": "apps/room/retry.html"}, {'room': room, 'wait': 10}) + + now = time.time() + lastvisit = room.lastvisit() + room.lastvisited = datetime.now() + + with ac_api_client(room.acc) as api: + api.poll_user_counts(room) + if clean: + # don't clean the room unless you get a good status code from the call to the usermanager api above + if room.self_cleaning and room.user_count == 0: + if (room.user_count == 0) and (abs(lastvisit - now) > settings.GRACE): + room.lock("Locked for cleaning") + try: + room = _clean(request,room) + except Exception,e: + room.unlock() + raise e + room.unlock() + + if room.host_count == 0 and room.allow_host: + return respond_to(request, {"text/html": "apps/room/launch.html"}, {'room': room}) + else: + room.save() + + key = None + if request.user.is_authenticated(): + key = _random_key(20) + user_principal = api.find_user(request.user.username) + principal_id = user_principal.get('principal-id') + with ac_api_client(room.acc) as api: + api.request("user-update-pwd",{"user-id": principal_id, 'password': key,'password-verify': key},True) + if promote and room.self_cleaning: + if user_principal: + api.request('permissions-update',{'acl-id': room.sco_id,'principal-id': user_principal.get('principal-id'),'permission-id':'host'},True) + + r = api.request('sco-info',{'sco-id':room.sco_id},True) + urlpath = r.et.findtext('.//sco/url-path') + start_user_counts_poll(room,10) + if key: + try: + user_client = ACPClient(room.acc.api_url, request.user.username, key, cache=False) + return user_client.redirect_to(room.acc.url+urlpath) + except Exception,e: + pass + return HttpResponseRedirect(room.acc.url+urlpath) + +## Tagging + +def _room2dict(room): + return {'name':room.name, + 'description':room.description, + 'user_count':room.nusers(), + 'host_count':room.nhosts(), + 'updated': rfc3339_date(room.lastupdated), + 'self_cleaning': room.self_cleaning, + 'url': room.go_url()} + +# should not require login +def list_by_tag(request,tn): + tags = tn.split('+') + rooms = TaggedItem.objects.get_by_model(Room, tags).order_by('name').all() + title = 'Rooms tagged with %s' % " and ".join(tags) + return respond_to(request, + {'text/html':'apps/room/list.html', + 'application/json': json_response([_room2dict(room) for room in rooms],request)}, + {'title':title, + 'description':title , + 'edit':False, + 'active':len(rooms) == 1, + 'baseurl': base_url(request), + 'tagstring': tn, + 'rooms':rooms}) + +# should not require login +def list_and_import_by_tag(request,tn): + tags = tn.split('+') + rooms = TaggedItem.objects.get_by_model(Room, tags).order_by('name').all() + for room in rooms: + _import_room(request,room.acc,{'sco_id': room.sco_id}) + title = 'Rooms tagged with %s' % " and ".join(tags) + return respond_to(request, + {'text/html':'apps/room/list.html', + 'application/json': json_response([_room2dict(room) for room in rooms],request)}, + {'title':title, + 'description':title , + 'edit':False, + 'active':len(rooms) == 1, + 'baseurl': base_url(request), + 'tagstring': tn, + 'rooms':rooms}) + +def widget(request,tags=None): + title = 'Meetingtools jQuery widget' + return respond_to(request,{'text/html':'apps/room/widget.html'},{'title': title,'tags':tags}) + +def _can_tag(request,tag): + if tag in ('selfcleaning','cleaning','public','private'): + return False,"'%s' is reserved" % tag + # XXX implement access model for tags here soon + return True,"" + +@login_required +def untag(request,rid,tag): + room = get_object_or_404(Room,pk=rid) + new_tags = [] + for t in Tag.objects.get_for_object(room): + if t.name != tag: + new_tags.append(t.name) + + Tag.objects.update_tags(room, ' '.join(new_tags)) + return redirect_to("/room/%d/tag" % room.id) + +@never_cache +@login_required +def tag(request,rid): + room = get_object_or_404(Room,pk=rid) + if request.method == 'POST': + form = TagRoomForm(request.POST) + if form.is_valid(): + for tag in re.split('[,\s]+',form.cleaned_data['tag']): + tag = tag.strip() + ok,reason = _can_tag(request,tag) + if ok: + Tag.objects.add_tag(room, tag) + else: + form._errors['tag'] = form.error_class([u'%s ... please choose another tag!' % reason]) + else: + form = TagRoomForm() + + tags = Tag.objects.get_for_object(room) + tn = "+".join([t.name for t in tags]) + return respond_to(request, + {'text/html': "apps/room/tag.html"}, + {'form': form,'formtitle': 'Add Tag','cancelname':'Done','submitname': 'Add Tag','room': room, 'tagstring': tn,'tags': tags}) + +def room_recordings(request,room): + with ac_api_client(room.acc) as api: + r = api.request('sco-expanded-contents',{'sco-id': room.sco_id,'filter-icon':'archive'},True) + return [{'name': sco.findtext('name'), + 'sco_id': sco.get('sco-id'), + 'url': room.acc.make_url(sco.findtext('url-path')), + 'description': sco.findtext('description'), + 'date_created': iso8601.parse_date(sco.findtext('date-created')), + 'date_modified': iso8601.parse_date(sco.findtext('date-modified'))} for sco in r.et.findall(".//sco")] + +@login_required +def recordings(request,rid): + room = get_object_or_404(Room,pk=rid) + return respond_to(request, + {'text/html': 'apps/room/recordings.html'}, + {'recordings': room_recordings(request,room),'room':room})
\ No newline at end of file |