summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeif Johansson <leifj@sunet.se>2012-10-16 17:57:45 +0200
committerLeif Johansson <leifj@sunet.se>2012-10-16 17:57:45 +0200
commit8f6f63c87128906be86fdfdf53aba48570677e87 (patch)
tree443ae2807ace7ed11306699c69cd4f209352874e
parenta15a4d81d5520adbfbbc5509202e32b1b56f826a (diff)
- normalize sco objects to separate tablesco
- add archive object for "published" archives - configurable return from form edits - tagging for archives - reset south
-rw-r--r--meetingtools/apps/archive/__init__.py1
-rw-r--r--meetingtools/apps/archive/admin.py10
-rw-r--r--meetingtools/apps/archive/forms.py6
-rw-r--r--meetingtools/apps/archive/management/__init__.py1
-rw-r--r--meetingtools/apps/archive/management/commands/__init__.py1
-rw-r--r--meetingtools/apps/archive/management/commands/update_recordings.py19
-rw-r--r--meetingtools/apps/archive/models.py50
-rw-r--r--meetingtools/apps/archive/views.py63
-rw-r--r--meetingtools/apps/room/forms.py4
-rw-r--r--meetingtools/apps/room/migrations/0001_initial.py122
-rw-r--r--meetingtools/apps/room/migrations/__init__.py0
-rw-r--r--meetingtools/apps/room/models.py38
-rw-r--r--meetingtools/apps/room/tasks.py23
-rw-r--r--meetingtools/apps/room/views.py273
-rw-r--r--meetingtools/apps/sco/__init__.py1
-rw-r--r--meetingtools/apps/sco/admin.py10
-rw-r--r--meetingtools/apps/sco/models.py98
-rw-r--r--meetingtools/settings.py6
-rw-r--r--meetingtools/urls.py3
-rw-r--r--templates/apps/archive/tag.html32
-rw-r--r--templates/apps/room/recordings.html25
-rw-r--r--templates/edit.html2
22 files changed, 606 insertions, 182 deletions
diff --git a/meetingtools/apps/archive/__init__.py b/meetingtools/apps/archive/__init__.py
new file mode 100644
index 0000000..3929ed7
--- /dev/null
+++ b/meetingtools/apps/archive/__init__.py
@@ -0,0 +1 @@
+__author__ = 'leifj'
diff --git a/meetingtools/apps/archive/admin.py b/meetingtools/apps/archive/admin.py
new file mode 100644
index 0000000..3ace40d
--- /dev/null
+++ b/meetingtools/apps/archive/admin.py
@@ -0,0 +1,10 @@
+'''
+Created on Jan 31, 2011
+
+@author: leifj
+'''
+
+from django.contrib import admin
+from meetingtools.apps.archive.models import Archive
+
+admin.site.register(Archive) \ No newline at end of file
diff --git a/meetingtools/apps/archive/forms.py b/meetingtools/apps/archive/forms.py
new file mode 100644
index 0000000..55086b8
--- /dev/null
+++ b/meetingtools/apps/archive/forms.py
@@ -0,0 +1,6 @@
+from django.forms import Form, CharField
+
+__author__ = 'leifj'
+
+class TagArchiveForm(Form):
+ tag = CharField(max_length=256) \ No newline at end of file
diff --git a/meetingtools/apps/archive/management/__init__.py b/meetingtools/apps/archive/management/__init__.py
new file mode 100644
index 0000000..3929ed7
--- /dev/null
+++ b/meetingtools/apps/archive/management/__init__.py
@@ -0,0 +1 @@
+__author__ = 'leifj'
diff --git a/meetingtools/apps/archive/management/commands/__init__.py b/meetingtools/apps/archive/management/commands/__init__.py
new file mode 100644
index 0000000..3929ed7
--- /dev/null
+++ b/meetingtools/apps/archive/management/commands/__init__.py
@@ -0,0 +1 @@
+__author__ = 'leifj'
diff --git a/meetingtools/apps/archive/management/commands/update_recordings.py b/meetingtools/apps/archive/management/commands/update_recordings.py
new file mode 100644
index 0000000..8aad747
--- /dev/null
+++ b/meetingtools/apps/archive/management/commands/update_recordings.py
@@ -0,0 +1,19 @@
+from django.core.management import BaseCommand
+from meetingtools.apps.archive.models import Archive
+from meetingtools.apps.cluster.models import ACCluster
+
+__author__ = 'leifj'
+
+class Command(BaseCommand):
+
+ def handle(self, *args, **options):
+ for ar in Archive.objects.all():
+ info = ar.sco.info()
+ if info is None:
+ continue
+ print info
+ if info.has_key('name'):
+ ar.name = info['name']
+ if info.has_key('description'):
+ ar.description = info['description']
+ ar.save()
diff --git a/meetingtools/apps/archive/models.py b/meetingtools/apps/archive/models.py
new file mode 100644
index 0000000..3bacec1
--- /dev/null
+++ b/meetingtools/apps/archive/models.py
@@ -0,0 +1,50 @@
+from datetime import datetime
+from iso8601 import iso8601
+from tagging.models import Tag
+from meetingtools.ac import ac_api_client
+from meetingtools.apps.room.models import Room
+from meetingtools.apps.sco.models import ACObject, get_sco, sco_mkdir
+
+__author__ = 'leifj'
+
+from django.db import models
+from django.db.models import ForeignKey, TextField, CharField
+
+class Archive(models.Model):
+ sco = ForeignKey(ACObject,editable=False,unique=True)
+ folder_sco = ForeignKey(ACObject,editable=False,related_name='archive_folder')
+ room = ForeignKey(Room,editable=False,related_name='archives')
+ description = TextField(blank=True,null=True)
+ name = CharField(max_length=128,blank=True,null=True)
+ urlpath = CharField(max_length=128)
+ timecreated = models.DateTimeField(auto_now_add=True)
+ lastupdated = models.DateTimeField(auto_now=True)
+
+ def __unicode__(self):
+ return "archive %s, sco %s, in folder %s" % (self.name,self.sco,self.folder_sco)
+
+def publish_archive(room,sco_id,tags=None):
+ acc = room.sco.acc
+ sco = get_sco(acc,sco_id)
+
+ info = sco.info(True)
+ dt = info['timecreated']
+ folder_sco = sco_mkdir(acc,"content/%d/%s/%s" % (dt.year,dt.month,dt.day))
+ with ac_api_client(acc) as api:
+ ar,create = Archive.objects.get_or_create(sco=sco,folder_sco=folder_sco,room=room)
+ ar.timecreated=info['timecreated']
+ if info['description']:
+ ar.description = info['description']
+ if info['name']:
+ ar.name = info['name']
+ ar.save()
+ try:
+ r = api.request('sco-move',{'sco-id':sco_id,'folder-id':folder_sco.sco_id},True)
+ except Exception,ex:
+ ar.delete()
+ raise ex
+
+ if tags is not None:
+ Tag.objects.update_tags(ar, ' '.join(tags))
+
+ return ar \ No newline at end of file
diff --git a/meetingtools/apps/archive/views.py b/meetingtools/apps/archive/views.py
new file mode 100644
index 0000000..2c8086d
--- /dev/null
+++ b/meetingtools/apps/archive/views.py
@@ -0,0 +1,63 @@
+import re
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import get_object_or_404
+from django.views.decorators.cache import never_cache
+from tagging.models import Tag
+from meetingtools.apps.archive.forms import TagArchiveForm
+from meetingtools.apps.archive.models import publish_archive, Archive
+from meetingtools.apps.room.models import Room
+from meetingtools.multiresponse import redirect_to, respond_to
+
+__author__ = 'leifj'
+
+class HttpRedirect(object):
+ pass
+
+@login_required
+def publish_sco(request,rid,sco_id):
+ room = get_object_or_404(Room,pk=rid)
+ acc = room.sco.sco_id
+ ar = publish_archive(room,sco_id)
+ return redirect_to("/room/%d/recordings#%d" % (rid,ar.sco.sco_id))
+
+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,""
+
+@never_cache
+@login_required
+def untag(request,rid,tag):
+ ar = get_object_or_404(Archive,pk=rid)
+ new_tags = []
+ for t in Tag.objects.get_for_object(ar):
+ if t.name != tag:
+ new_tags.append(t.name)
+
+ Tag.objects.update_tags(ar, ' '.join(new_tags))
+ return redirect_to("/archive/%d/tag" % ar.id)
+
+@never_cache
+@login_required
+def tag(request,rid):
+ archive = get_object_or_404(Archive,pk=rid)
+ if request.method == 'POST':
+ form = TagArchiveForm(request.POST)
+ if form.is_valid():
+ for tag in re.split('[,\s]+',form.cleaned_data['tag']):
+ tag = tag.strip()
+ if tag:
+ ok,reason = _can_tag(request,tag)
+ if ok:
+ Tag.objects.add_tag(archive, tag)
+ else:
+ form._errors['tag'] = form.error_class([u'%s ... please choose another tag!' % reason])
+ else:
+ form = TagArchiveForm()
+
+ tags = Tag.objects.get_for_object(archive)
+ tn = "+".join([t.name for t in tags])
+ return respond_to(request,
+ {'text/html': "apps/archive/tag.html"},
+ {'form': form,'formtitle': 'Add Tag','cancelname':'Done','submitname': 'Add Tag','archive': archive, 'tagstring': tn,'tags': tags})
diff --git a/meetingtools/apps/room/forms.py b/meetingtools/apps/room/forms.py
index 62b515b..0e44aea 100644
--- a/meetingtools/apps/room/forms.py
+++ b/meetingtools/apps/room/forms.py
@@ -28,8 +28,8 @@ class PrefixTextInput(TextInput):
class ModifyRoomForm(ModelForm):
class Meta:
model = Room
- fields = ['name','description','source_sco_id','self_cleaning','allow_host']
- widgets = {'source_sco_id': Select(),
+ fields = ('name','description','source_sco','self_cleaning','allow_host')
+ widgets = {'source_sco': Select(),
'description': Textarea(attrs={'rows': 4, 'cols': 50}),
'name': TextInput(attrs={'size': '40'})}
diff --git a/meetingtools/apps/room/migrations/0001_initial.py b/meetingtools/apps/room/migrations/0001_initial.py
new file mode 100644
index 0000000..92e9856
--- /dev/null
+++ b/meetingtools/apps/room/migrations/0001_initial.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Room'
+ db.create_table('room_room', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('creator', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('sco', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sco.ACObject'], null=True)),
+ ('folder_sco', self.gf('django.db.models.fields.related.ForeignKey')(related_name='folders', null=True, to=orm['sco.ACObject'])),
+ ('source_sco', self.gf('django.db.models.fields.related.ForeignKey')(related_name='sources', null=True, to=orm['sco.ACObject'])),
+ ('deleted_sco', self.gf('django.db.models.fields.related.ForeignKey')(related_name='deleted', null=True, to=orm['sco.ACObject'])),
+ ('urlpath', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)),
+ ('self_cleaning', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('allow_host', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+ ('user_count', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
+ ('host_count', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
+ ('timecreated', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('lastupdated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+ ('lastvisited', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ))
+ db.send_create_signal('room', ['Room'])
+
+ # Adding unique constraint on 'Room', fields ['name', 'folder_sco']
+ db.create_unique('room_room', ['name', 'folder_sco_id'])
+
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'Room', fields ['name', 'folder_sco']
+ db.delete_unique('room_room', ['name', 'folder_sco_id'])
+
+ # Deleting model 'Room'
+ db.delete_table('room_room')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'cluster.accluster': {
+ 'Meta': {'object_name': 'ACCluster'},
+ 'api_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+ 'default_template_sco_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'blank': 'True'}),
+ 'domain_match': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+ 'user': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'room.room': {
+ 'Meta': {'unique_together': "(('name', 'folder_sco'),)", 'object_name': 'Room'},
+ 'allow_host': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'deleted_sco': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted'", 'null': 'True', 'to': "orm['sco.ACObject']"}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'folder_sco': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'folders'", 'null': 'True', 'to': "orm['sco.ACObject']"}),
+ 'host_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'lastupdated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'lastvisited': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'sco': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sco.ACObject']", 'null': 'True'}),
+ 'self_cleaning': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'source_sco': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sources'", 'null': 'True', 'to': "orm['sco.ACObject']"}),
+ 'timecreated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'urlpath': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'user_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'sco.acobject': {
+ 'Meta': {'unique_together': "(('acc', 'sco_id'),)", 'object_name': 'ACObject'},
+ 'acc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cluster.ACCluster']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'lastupdated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'sco_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'timecreated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['room'] \ No newline at end of file
diff --git a/meetingtools/apps/room/migrations/__init__.py b/meetingtools/apps/room/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/meetingtools/apps/room/migrations/__init__.py
diff --git a/meetingtools/apps/room/models.py b/meetingtools/apps/room/models.py
index 2177b24..29fe1c7 100644
--- a/meetingtools/apps/room/models.py
+++ b/meetingtools/apps/room/models.py
@@ -9,7 +9,7 @@ from django.db.models.fields import CharField, BooleanField, IntegerField,\
TextField
from django.db.models.fields.related import ForeignKey
from django.contrib.auth.models import User
-from meetingtools.apps.cluster.models import ACCluster
+from meetingtools.apps.sco.models import ACObject, get_sco
import time
import tagging
from meetingtools.settings import LOCK_DIR
@@ -43,15 +43,14 @@ class RoomLockedException(Exception):
class Room(models.Model):
creator = ForeignKey(User,editable=False)
- name = CharField(max_length=128)
+ name = CharField(max_length=128)
+ sco = ForeignKey(ACObject,editable=False,null=True)
+ folder_sco = ForeignKey(ACObject,null=True,related_name="folders")
+ source_sco = ForeignKey(ACObject,null=True,related_name="sources")
+ deleted_sco = ForeignKey(ACObject,null=True,related_name="deleted")
urlpath = CharField(verbose_name="Custom URL",max_length=128,unique=True)
- acc = ForeignKey(ACCluster,verbose_name="Adobe Connect Cluster",editable=False)
self_cleaning = BooleanField(verbose_name="Clean-up when empty?")
allow_host = BooleanField(verbose_name="Allow first participant to become host?",default=True)
- sco_id = IntegerField(verbose_name="Adobe Connect Room")
- source_sco_id = IntegerField(verbose_name="Template",blank=True,null=True)
- deleted_sco_id = IntegerField(verbose_name="Previous Room ID",editable=False,blank=True,null=True)
- folder_sco_id = IntegerField(verbose_name="Adobe Connect Room Folder",editable=False)
description = TextField(blank=True,null=True)
user_count = IntegerField(verbose_name="User Count At Last Visit",editable=False,blank=True,null=True)
host_count = IntegerField(verbose_name="Host Count At Last Visit",editable=False,blank=True,null=True)
@@ -60,11 +59,24 @@ class Room(models.Model):
lastvisited = models.DateTimeField(blank=True,null=True)
class Meta:
- unique_together = (('acc','sco_id'),('name','folder_sco_id'))
-
+ unique_together = (('name','folder_sco'))
+
def __unicode__(self):
- return "%s (sco_id=%s,source_sco_id=%s,folder_sco_id=%s,urlpath=%s)" % (self.name,self.sco_id,self.source_sco_id,self.folder_sco_id,self.urlpath)
-
+ return "%s (sco_id=%s,source_sco_id=%s,folder_sco_id=%s,urlpath=%s)" % \
+ (self.name,self.sco.sco_id,self.source_sco.sco_id,self.folder_sco.sco_id,self.urlpath)
+
+ @staticmethod
+ def by_sco(sco):
+ return Room.objects.get(sco=sco)
+
+ @staticmethod
+ def by_id(acc,sco_id):
+ return Room.by_sco(get_sco(acc,sco_id))
+
+ @staticmethod
+ def by_name(acc,name):
+ Room.objects.get(sco__acc=acc,name=name)
+
def _lockf(self):
return "%s%sroom-%d.lock" % (LOCK_DIR,os.sep,+self.id)
@@ -114,13 +126,13 @@ class Room(models.Model):
return "/room/%d/recordings" % self.id
def nusers(self):
- if self.user_count == None:
+ if self.user_count is None:
return "unknown many"
else:
return self.user_count
def nhosts(self):
- if self.host_count == None:
+ if self.host_count is None:
return "unknown many"
else:
return self.host_count
diff --git a/meetingtools/apps/room/tasks.py b/meetingtools/apps/room/tasks.py
index ce9f275..a7404a0 100644
--- a/meetingtools/apps/room/tasks.py
+++ b/meetingtools/apps/room/tasks.py
@@ -5,6 +5,7 @@ Created on Jan 18, 2012
'''
from celery.task import periodic_task,task
from celery.schedules import crontab
+from meetingtools.apps.sco.models import get_sco
from meetingtools.apps.cluster.models import ACCluster
from meetingtools.ac import ac_api_client
from meetingtools.apps.room.models import Room
@@ -58,7 +59,7 @@ def _extended_info(api,sco_id):
r = api.request('sco-info',{'sco-id':sco_id},False)
if r.status_code == 'no-data':
return None
- return (r.et,_owner_username(api,r.et.xpath('//sco')[0]))
+ return r.et,_owner_username(api,r.et.xpath('//sco')[0])
def _import_one_room(acc,api,row):
sco_id = int(row.get('sco-id'))
@@ -66,7 +67,7 @@ def _import_one_room(acc,api,row):
room = None
try:
- room = Room.objects.get(acc=acc,deleted_sco_id=sco_id)
+ room = Room.objects.get(deleted_sco__acc=acc,deleted_sco__sco_id=sco_id)
if room is not None:
return # We hit a room in the process of being cleaned - let it simmer until next pass
except ObjectDoesNotExist:
@@ -77,7 +78,7 @@ def _import_one_room(acc,api,row):
try:
logging.debug("finding acc=%s,sco_id=%d in our DB" % (acc,sco_id))
- room = Room.objects.get(acc=acc,sco_id=sco_id)
+ room = Room.objects.get(sco__acc=acc,sco__sco_id=sco_id)
if room.deleted_sco_id is not None:
return # We hit a room in the process of being cleaned - let it simmer until next pass
room.trylock()
@@ -112,7 +113,6 @@ def _import_one_room(acc,api,row):
else:
return int(str)
-
for elt in r.findall(".//sco[0]"):
folder_sco_id = _ior0(elt,'folder-id',0)
source_sco_id = _ior0(elt,'source-sco-id',0)
@@ -124,7 +124,12 @@ def _import_one_room(acc,api,row):
user,created = User.objects.get_or_create(username=username)
if created:
user.set_unusable_password()
- room = Room.objects.create(acc=acc,sco_id=sco_id,creator=user,name=name,description=description,folder_sco_id=folder_sco_id,source_sco_id=source_sco_id,urlpath=urlpath)
+ room = Room.objects.create(sco=get_sco(acc,sco_id),
+ creator=user,name=name,
+ description=description,
+ folder_sco=get_sco(acc,folder_sco_id),
+ source_sco=get_sco(acc,source_sco_id)
+ ,urlpath=urlpath)
room.trylock()
else:
if folder_sco_id:
@@ -167,7 +172,7 @@ def start_user_counts_poll(room,niter):
@task(name='meetingtools.apps.room.tasks.poll_user_counts',rate_limit="10/s")
def poll_user_counts(room,niter=0):
logging.debug("polling user_counts for room %s" % room.name)
- with ac_api_client(room.acc) as api:
+ with ac_api_client(room.sco.acc) as api:
(nusers,nhosts) = api.poll_user_counts(room)
if nusers > 0:
logging.debug("room occupied by %d users and %d hosts, checking again in 20 ..." % (nusers,nhosts))
@@ -183,7 +188,7 @@ def import_recent_user_counts():
for acc in ACCluster.objects.all():
with ac_api_client(acc) as api:
then = datetime.now()-timedelta(seconds=600)
- for room in Room.objects.filter((Q(lastupdated__gt=then) | Q(lastvisited__gt=then)) & Q(acc=acc)):
+ for room in Room.objects.filter((Q(lastupdated__gt=then) | Q(lastvisited__gt=then)) & Q(sco__acc=acc)):
api.poll_user_counts(room)
# look for sessions that are newer than the one we know about for a room
@@ -192,7 +197,7 @@ def import_sessions():
for room in Room.objects.all():
with ac_api_client(room.acc) as api:
p = {'sco-id': room.sco_id,'sort-date-created': 'asc'}
- if room.lastvisited != None:
+ if room.lastvisited is not None:
last = room.lastvisited
last.replace(microsecond=0)
p['filter-gt-date-created'] = last.isoformat()
@@ -218,7 +223,7 @@ def import_transactions():
if not seen.get(sco_id,False): #pick the first session for each room - ie the one last created
seen[sco_id] = True
try:
- room = Room.objects.get(acc=acc,sco_id=sco_id)
+ room = Room.objects.get(sco__acc=acc,sco__sco_id=sco_id)
date_created = iso8601.parse_date(row.findtext("date-created"))
room.lastvisited = date_created
room.save()
diff --git a/meetingtools/apps/room/views.py b/meetingtools/apps/room/views.py
index 048fc05..bf9bc61 100644
--- a/meetingtools/apps/room/views.py
+++ b/meetingtools/apps/room/views.py
@@ -3,14 +3,15 @@ Created on Jan 31, 2011
@author: leifj
"""
-from meetingtools.apps.room.models import Room, ACCluster
+from celery.utils import deprecated
+from meetingtools.apps.sco.models import get_sco, get_sco_shortcuts
+from meetingtools.apps.room.models import Room
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
@@ -20,7 +21,7 @@ from django.conf import settings
from django.utils.datetime_safe import datetime
from django.http import HttpResponseRedirect
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
-from django_co_acls.models import allow, deny, acl, clear_acl
+from django_co_acls.models import allow, acl, clear_acl
from meetingtools.ac.api import ACPClient
from tagging.models import Tag, TaggedItem
import random, string
@@ -44,45 +45,21 @@ def _user_meeting_folder(request,acc):
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):
+def _user_templates(request,acc,folder_sco):
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 folder_sco:
+ my_templates = api.request('sco-contents',{'sco-id': folder_sco.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')]
+ templates += [(get_sco(acc,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')]
+ shared_templates_sco = get_sco_shortcuts(acc,'shared-meeting-templates')
+ shared_templates = api.request('sco-contents',{'sco-id': shared_templates_sco.sco_id,'filter-type': 'meeting'})
+ if shared_templates:
+ templates += [(get_sco(acc,r.get('sco-id')).id,r.findtext('name')) for r in shared_templates.et.findall('.//sco')]
return templates
@@ -113,45 +90,54 @@ def view(request,id):
'active': True,
})
-def _init_update_form(request,form,acc,my_meetings_sco_id):
+def _init_update_form(request,form,acc,folder_sco):
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)]
+ if form.fields.has_key('source_sco'):
+ form.fields['source_sco'].widget.choices = [('','-- select template --')]+[r for r in _user_templates(request,acc,folder_sco)]
-def _update_room(request, room, data=dict()):
+def _update_room(request, room, data=dict(), acc=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')):
+
+ if acc is None:
+ acc = acc_for_user(request.user)
+
+ for attr,param in (('sco','sco-id'),('folder_sco','folder-id'),('source_sco','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))
+ logging.debug("%s,%s = %s" % (attr,param,repr(v)))
if data.has_key(attr) and data[attr]:
v = data[attr]
if v:
if isinstance(v,(str,unicode)):
params[param] = v
+ elif hasattr(v,'sco_id'):
+ params[param] = v.sco_id # support ACObject instances
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:
+ with ac_api_client(acc) as api:
r = api.request('sco-update', params, True)
- sco = r.et.find(".//sco")
- if sco:
- sco_id = sco.get('sco-id')
+ sco_elt = r.et.find(".//sco")
+ if sco_elt:
+ sco_id = sco_elt.get('sco-id')
if sco_id:
- data['sco_id'] = sco_id
- data['source_sco_id'] = r.et.find(".//sco").get('sco-source-id')
- room.sco_id = sco_id
- room.save()
+ data['sco'] = get_sco(acc,sco_id)
- sco_id = room.sco_id
+ source_sco_id = r.et.find(".//sco").get('sco-source-id')
+ if source_sco_id:
+ data['source_sco'] = get_sco(acc,source_sco_id)
+
+ room.sco = data['sco']
+ room.save()
+
+ sco_id = room.sco.sco_id
assert(sco_id is not None and sco_id > 0)
@@ -185,7 +171,7 @@ def _update_room(request, room, data=dict()):
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.deleted_sco = 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
@@ -193,47 +179,52 @@ def _update_room(request, room, data=dict()):
@login_required
def create(request):
acc = acc_for_user(request.user)
- my_meetings_sco_id = _user_meeting_folder(request,acc)
+ my_meetings_sco = get_sco(acc,_user_meeting_folder(request,acc))
template_sco_id = acc.default_template_sco_id
if not template_sco_id:
template_sco_id = settings.DEFAULT_TEMPLATE_SCO
- room = Room(creator=request.user,acc=acc,folder_sco_id=my_meetings_sco_id,source_sco_id=template_sco_id)
+ room = Room(creator=request.user,folder_sco=my_meetings_sco,source_sco=get_sco(acc,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)
+ _init_update_form(request, form, acc, my_meetings_sco)
if form.is_valid():
_update_room(request, room, form.cleaned_data)
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)
+ _init_update_form(request, form, acc, my_meetings_sco)
- return respond_to(request,{'text/html':'apps/room/create.html'},{'form':form,'formtitle': title,'cancelname':'Cancel','submitname':'%s Room' % what})
+ return respond_to(request,{'text/html':'apps/room/create.html'},
+ {'form':form,
+ 'formtitle': title,
+ 'cancelurl': '/rooms',
+ 'cancelname':'Cancel',
+ 'submitname':'%s Room' % what})
@never_cache
@login_required
def myroom(request):
acc = acc_for_user(request.user)
- my_meetings_sco_id = _user_meeting_folder(request,acc)
+ my_meetings_sco = get_sco(acc,_user_meeting_folder(request,acc))
template_sco_id = acc.default_template_sco_id
if not template_sco_id:
template_sco_id = settings.DEFAULT_TEMPLATE_SCO
-
+ template_sco = get_sco(acc,template_sco_id)
room = None
try:
- room = Room.objects.get(acc=acc,name=request.user.username)
+ room = Room.by_name(acc,name=request.user.username)
except MultipleObjectsReturned:
raise ValueError("Oops - there seem to be multiple rooms with name '%s'" % request.user.username)
except ObjectDoesNotExist:
room = Room(creator=request.user,
acc=acc,
- folder_sco_id=my_meetings_sco_id,
+ folder_sco=my_meetings_sco,
name=request.user.username,
- source_sco_id=template_sco_id)
+ source_sco=template_sco)
_update_room(request,room,dict(access='public'))
if not room:
@@ -245,23 +236,29 @@ def myroom(request):
@login_required
def update(request,id):
room = get_object_or_404(Room,pk=id)
- acc = room.acc
+ acc = room.sco.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)
+ _init_update_form(request, form, acc, room.folder_sco)
if form.is_valid():
_update_room(request, room, form.cleaned_data)
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)
+ _init_update_form(request, form, acc, room.folder_sco)
- return respond_to(request,{'text/html':'apps/room/update.html'},{'form':form,'formtitle': title,'cancelname': 'Cancel','submitname':'%s Room' % what})
-
+ return respond_to(request,{'text/html':'apps/room/update.html'},
+ {'form':form,
+ 'formtitle': title,
+ 'cancelurl': '/rooms#%d' % room.id,
+ 'cancelname': 'Cancel',
+ 'submitname':'%s Room' % what})
+
+@deprecated
def _import_room(request,acc,r):
modified = False
room = None
@@ -273,8 +270,8 @@ def _import_room(request,acc,r):
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'):
+ room = Room.by_sco(r['sco'])
+ for key in ('sco','name','source_sco','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] is not None and r[key]:
@@ -286,16 +283,15 @@ def _import_room(request,acc,r):
room.save()
except ObjectDoesNotExist:
- if r['folder_sco_id']:
+ if r['folder_sco']:
try:
- room = Room.objects.create(sco_id=r['sco_id'],
- source_sco_id=r['source_sco_id'],
- acc=acc,
+ room = Room.objects.create(sco=r['sco'],
+ source_sco=r['source_sco'],
name=r['name'],
urlpath=r['urlpath'],
description=r['description'],
creator=request.user,
- folder_sco_id=r['folder_sco_id'])
+ folder_sco=r['folder_sco'])
except Exception,e:
room = None
pass
@@ -305,7 +301,7 @@ def _import_room(request,acc,r):
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)
+ userlist = api.request('meeting-usermanager-user-list',{'sco-id': room.sco.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'])"))
@@ -331,35 +327,6 @@ def list_rooms(request,username=None):
{'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()
@@ -371,28 +338,37 @@ def delete(request,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)
+ with ac_api_client(room.sco.acc) as api:
+ api.request('sco-delete',{'sco-id':room.sco.sco_id},raise_error=True)
clear_acl(room)
+ del room.sco
+ if room.folder_sco is not None:
+ del room.folder_sco
+ if room.deleted_sco is not None:
+ del room.deleted_sco
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'})
+ return respond_to(request,{'text/html':'edit.html'},
+ {'form':form,
+ 'formtitle': 'Delete %s' % room.name,
+ 'cancelurl': '/rooms',
+ 'cancelname':'Cancel',
+ 'submitname':'Delete Room'})
def _clean(request,room):
- with ac_api_client(room.acc) as api:
- room.deleted_sco_id = room.sco_id
+ with ac_api_client(room.sco.acc) as api:
+ room.deleted_sco = room.sco
room.save()
- api.request('sco-delete',{'sco-id':room.sco_id},raise_error=False)
- room.sco_id = None
+ api.request('sco-delete',{'sco-id':room.sco.sco_id},raise_error=False)
+ room.sco = 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:
+ with ac_api_client(room.sco.acc) as api:
api.poll_user_counts(room)
d = {'nusers': room.user_count, 'nhosts': room.host_count}
return respond_to(request,
@@ -433,7 +409,7 @@ def _goto(request,room,clean=True,promote=False):
lastvisit = room.lastvisit()
room.lastvisited = datetime.now()
- with ac_api_client(room.acc) as api:
+ with ac_api_client(room.sco.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
@@ -455,22 +431,22 @@ def _goto(request,room,clean=True,promote=False):
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:
+ with ac_api_client(room.sco.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)
+ api.request('permissions-update',{'acl-id': room.sco.sco_id,'principal-id': user_principal.get('principal-id'),'permission-id':'host'},True)
- r = api.request('sco-info',{'sco-id':room.sco_id},True)
+ r = api.request('sco-info',{'sco-id':room.sco.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)
+ user_client = ACPClient(room.sco.acc.api_url, request.user.username, key, cache=False)
+ return user_client.redirect_to(room.sco.acc.url+urlpath)
except Exception,e:
pass
- return HttpResponseRedirect(room.acc.url+urlpath)
+ return HttpResponseRedirect(room.sco.acc.url+urlpath)
## Tagging
@@ -499,23 +475,6 @@ def list_by_tag(request,tn):
'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'
@@ -560,19 +519,37 @@ def tag(request,rid):
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})
+ {'form': form,
+ 'formtitle': 'Add Tag',
+ 'cancelurl': '/rooms#%d' % room.id,
+ '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')),
- 'dl': room.acc.make_dl_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")]
-
+ acc = room.sco.acc
+ with ac_api_client(acc) as api:
+ r = api.request('sco-expanded-contents',{'sco-id': room.sco.sco_id,'filter-icon':'archive'},True)
+ return [{'published': False,
+ 'name': sco_elt.findtext('name'),
+ 'sco': get_sco(acc,sco_elt.get('sco-id')),
+ 'url': room.sco.acc.make_url(sco_elt.findtext('url-path')),
+ 'dl': room.sco.acc.make_dl_url(sco_elt.findtext('url-path')),
+ 'description': sco_elt.findtext('description'),
+ 'date_created': iso8601.parse_date(sco_elt.findtext('date-created')),
+ 'date_modified': iso8601.parse_date(sco_elt.findtext('date-modified'))} for sco_elt in r.et.findall(".//sco")] + [
+ {'published': True,
+ 'ar': ar,
+ 'name': ar.name,
+ 'description': ar.description,
+ 'sco': ar.sco,
+ 'url': room.sco.acc.make_url(ar.urlpath),
+ 'dl': room.sco.acc.make_url(ar.urlpath),
+ 'date_created': ar.timecreated,
+ 'date_modified': ar.lastupdated} for ar in room.archives.all()
+ ]
@login_required
def recordings(request,rid):
room = get_object_or_404(Room,pk=rid)
diff --git a/meetingtools/apps/sco/__init__.py b/meetingtools/apps/sco/__init__.py
new file mode 100644
index 0000000..3929ed7
--- /dev/null
+++ b/meetingtools/apps/sco/__init__.py
@@ -0,0 +1 @@
+__author__ = 'leifj'
diff --git a/meetingtools/apps/sco/admin.py b/meetingtools/apps/sco/admin.py
new file mode 100644
index 0000000..98da992
--- /dev/null
+++ b/meetingtools/apps/sco/admin.py
@@ -0,0 +1,10 @@
+'''
+Created on Jan 31, 2011
+
+@author: leifj
+'''
+
+from django.contrib import admin
+from meetingtools.apps.sco.models import ACObject
+
+admin.site.register(ACObject) \ No newline at end of file
diff --git a/meetingtools/apps/sco/models.py b/meetingtools/apps/sco/models.py
new file mode 100644
index 0000000..325baae
--- /dev/null
+++ b/meetingtools/apps/sco/models.py
@@ -0,0 +1,98 @@
+"""
+Abstract sco objects and utility methods
+"""
+
+__author__ = 'leifj'
+
+from meetingtools.ac import ac_api_client
+from meetingtools.apps.cluster.models import ACCluster
+from django.db import models
+from django.db.models import fields, ForeignKey
+from django.core.cache import cache
+from datetime import datetime
+from iso8601 import iso8601
+
+class ACObject(models.Model):
+ acc = ForeignKey(ACCluster,editable=False)
+ sco_id = fields.IntegerField()
+ is_deleted = fields.BooleanField(default=False,editable=False)
+ timecreated = models.DateTimeField(auto_now_add=True)
+ lastupdated = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ unique_together = ('acc','sco_id')
+
+ def __unicode__(self):
+ return "%s#%d" % (self.acc,self.sco_id)
+
+ def info(self,raise_errors=False):
+ with ac_api_client(self.acc) as api:
+ r = api.request('sco-info',{'sco-id':self.sco_id},raise_errors)
+ if r.status_code == 'no-data':
+ if raise_errors:
+ raise ValueError("No data about %s" % self)
+ else:
+ return None
+
+ d = dict()
+ for sco_elt in r.et.findall(".//sco"): #only one but this degrades nicely
+ dt = datetime.now() # a fallback just in case
+ dt_text = sco_elt.findtext('date-created')
+ if dt_text is not None and len(dt_text) > 0:
+ dt = iso8601.parse_date(sco_elt.findtext('date-created'))
+ d['timecreated']=dt
+ for a in ('description','name','url-path'):
+ v = sco_elt.findtext(a)
+ if v is not None:
+ d[a] = v
+ return d
+
+def get_sco(acc,sco_id):
+ key = "ac:sco:%s/%s" % (acc,sco_id)
+ sco = cache.get(key)
+ if sco is None:
+ sco,created = ACObject.objects.get_or_create(acc=acc,sco_id=sco_id)
+ assert sco is not None
+ cache.set(key,sco)
+ return sco
+
+def get_sco_shortcuts(acc,shortcut_id):
+ key = "ac:shortcuts:%s" % acc
+ shortcuts = cache.get(key)
+ if not shortcuts:
+ shortcuts = {}
+ with ac_api_client(acc) as api:
+ r = api.request('sco-shortcuts')
+ for sco_elt in r.et.findall(".//sco"):
+ shortcuts[sco_elt.get('type')] = get_sco(acc,sco_elt.get('sco-id'))
+ cache.set(key,shortcuts)
+ return shortcuts.get(shortcut_id,None)
+
+def _mkdir(api,folder_sco_id,name):
+ r = api.request('sco-update',{'name':name,'folder-id':folder_sco_id,'type':'folder'},True)
+ sco_elt = r.et.find(".//sco")
+ assert(sco_elt is not None)
+ sco_id = sco_elt.get('sco-id')
+ assert sco_id > 0
+ return sco_id
+
+def _isdir(api,folder_sco_id,name):
+ r = api.request('sco-contents',{'sco-id':folder_sco_id,'filter-type':'folder','filter-name':name},True)
+ sco_elt = r.et.find(".//sco")
+ if sco_elt is None:
+ return None
+ return sco_elt.get('sco-id')
+
+def sco_mkdir(acc,path):
+ p = path.split("/")
+ p0 = p.pop(0)
+ folder_sco = get_sco_shortcuts(acc,p0) #note that first part of path must be the @type of the tree, not the name
+ assert folder_sco is not None,ValueError("Unable to find shortcut '%s" % p0)
+ folder_sco_id = folder_sco.sco_id
+ with ac_api_client(acc) as api:
+ for n in p:
+ sco_id = _isdir(api,folder_sco_id,n)
+ if sco_id is None:
+ sco_id = _mkdir(api,folder_sco_id,n)
+ folder_sco_id = sco_id
+ return get_sco(acc,folder_sco_id) \ No newline at end of file
diff --git a/meetingtools/settings.py b/meetingtools/settings.py
index 873a74d..f8e023a 100644
--- a/meetingtools/settings.py
+++ b/meetingtools/settings.py
@@ -4,7 +4,7 @@ from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
import meetingtools.site_logging
import os
-DEBUG = False
+DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
@@ -135,7 +135,9 @@ INSTALLED_APPS = (
'meetingtools.apps.room',
'meetingtools.apps.cluster',
'meetingtools.apps.userprofile',
- 'meetingtools.apps.stats'
+ 'meetingtools.apps.stats',
+ 'meetingtools.apps.sco',
+ 'meetingtools.apps.archive'
)
CARROT_BACKEND = "django"
diff --git a/meetingtools/urls.py b/meetingtools/urls.py
index dbea187..c4a2855 100644
--- a/meetingtools/urls.py
+++ b/meetingtools/urls.py
@@ -37,8 +37,11 @@ urlpatterns = patterns('',
(r'^room/(\d+)/modify$','meetingtools.apps.room.views.update'),
(r'^room/(\d+)/delete$','meetingtools.apps.room.views.delete'),
(r'^room/(\d+)/unlock$','meetingtools.apps.room.views.unlock'),
+ (r'^room/(\d+)/publish/(\d+)','meetingtools.apps.archive.views.publish_sco'),
(r'^room/(\d+)/tag$','meetingtools.apps.room.views.tag'),
(r'^room/(\d+)/untag/(.+)$','meetingtools.apps.room.views.untag'),
+ (r'^archive/(\d+)/tag$','meetingtools.apps.archive.views.tag'),
+ (r'^archive/(\d+)/untag/(.+)$','meetingtools.apps.archive.views.untag'),
(r'^room/(\d+)/recordings$','meetingtools.apps.room.views.recordings'),
(r'^room/\+(.+)\.(?:json|html|htm)$','meetingtools.apps.room.views.list_by_tag'),
(r'^room/\+(.+)\.(?:atom)$',RoomAtomTagFeed()),
diff --git a/templates/apps/archive/tag.html b/templates/apps/archive/tag.html
new file mode 100644
index 0000000..abd7602
--- /dev/null
+++ b/templates/apps/archive/tag.html
@@ -0,0 +1,32 @@
+{% extends "edit.html" %}
+{% load datehumanize %}
+{% load roomurl %}
+{% block widgets %}
+
+{% endblock %}
+{% block formstyle %}
+class="form-inline"
+{% endblock %}
+{% block justbeforeform %}
+<div>
+ <div class="alert">
+ <i class="icon-info-sign"> </i>
+ <strong>Note Well</strong> Tags are a way to group related recordings together. Tagging your
+ recording makes your recording show up in public lists of recordings hosted on this service and it
+ therefore makes your recording visible. This does <em>not</em> mean that anyone can access
+ your recording.
+ </div>
+ {% if tags %}
+ <div style="margin-bottom: 20px;">
+ {% for tag in tags %}
+ <span class="btn-group">
+ <a class="btn btn-small" href="/archive/+{{tag}}">{{tag}}</a>&nbsp;
+ <a class="btn btn-small btn-warning" href="/archive/{{archive.id}}/untag/{{tag}}"><i class="icon-remove"></i></a>
+ </span>
+ {% endfor %}
+ </div>
+ {% else %}
+ <div class="alert">There are no tags yet...</div>
+</div>
+ {% endif %}
+{% endblock %}
diff --git a/templates/apps/room/recordings.html b/templates/apps/room/recordings.html
index 3b83919..0ee4ebd 100644
--- a/templates/apps/room/recordings.html
+++ b/templates/apps/room/recordings.html
@@ -17,19 +17,30 @@
<div id="recordings" class="accordion">
{% for r in recordings %}
<div class="recording accordion-group">
- <div class="accordion-heading"><a class="accordion-toggle" data-toggle="collapse" data-target="#{{r.sco_id}}" data-parent="#recordings" href="#{{r.sco_id}}">{{r.name}}</a></div>
- <div id="{{r.sco_id}}" class="accordion-body collapse">
+ <div class="accordion-heading"><a class="accordion-toggle" data-toggle="collapse" data-target="#{{r.sco.sco_id}}" data-parent="#recordings" href="#{{r.sco_id}}">{{r.name}}</a></div>
+ <div id="{{r.sco.sco_id}}" class="accordion-body collapse">
<div class="accordion-inner">
+
<p><a target="_connect" href="{{r.url}}">{{r.url}}</a></p>
<div class="well">{% if r.description %}{{r.description|safe}}{% else %}<em>No description available...</em>{% endif %}</div>
<ul class="unstyled">
<li>&raquo;&nbsp;Created {{r.date_created|datehumanize}}.</li>
<li>&raquo;&nbsp;Modified {{r.date_modified|datehumanize}}.</li>
- <li>&raquo;&nbsp;Hosted on {{room.acc.name}}</li>
- </ul>
+ <li>&raquo;&nbsp;Hosted on {{room.sco.acc.name}}</li>
+ {% if r.published %}
+ {% tags_for_object r.ar as tags %}
+ {% if tags %}<li>&raquo;&nbsp;{% for tag in tags %}<a class="badge" href="/archive/+{{tag}}">{{tag}}</a>{% endfor %}{%if edit %}&nbsp;<a class="btn btn-mini btn-info" href="/archive/{{r.id}}/tag">manage archive tags</a>{%endif%}</li>{% endif %}
+ {% endif %}
+ </ul>
+
<div class="btn-group">
- <a class="btn btn-info" target="_connect" href="{{r.url}}">Play Recording</a>
- <a class="btn btn-info" href="{{r.dl}}">Download Recording</a>
+ <a class="btn" target="_connect" href="{{r.url}}"><i class="icon-play"> </i> Play</a>
+ <a class="btn" href="{{r.dl}}"><i class="icon-download"> </i> Download</a>
+ {% if not r.published %}
+ <a class="btn btn-success" href="/room/{{room.id}}/publish/{{r.sco.sco_id}}"><i class="icon-upload"> </i> Publish</a>
+ {% else %}
+ <a class="btn" href="/archive/{{r.ar.id}}/tag">Manage Tags</a>
+ {% endif %}
</div>
</div>
</div>
@@ -40,7 +51,7 @@
<p>No recordings right now...</p>
{% endif %}
<br/>
- <a class="btn" href="/room#{{room.id}}">Back to '{{room.name}}'</a>
+ <a class="btn" href="/room#{{room.id}}"><i class="icon-step-backward"> </i> Back to '{{room.name}}'</a>
{% endblock %}
{% block validators %}
<a href="http://feed1.w3.org/check.cgi?url=/room/{{room.id}}/recordings.atom"><img src="{{STATIC_URL}}/img/valid-atom.png" alt="[Valid Atom 1.0]" title="Validate my Atom 1.0 feed" /></a>
diff --git a/templates/edit.html b/templates/edit.html
index 5d04b3f..3542214 100644
--- a/templates/edit.html
+++ b/templates/edit.html
@@ -21,7 +21,7 @@
{% endif %}
{% endfor %}
<div class="form-actions">
- <input type="button" class="btn" onClick="document.location='/rooms'" value="{{cancelname}}"/>
+ <input type="button" class="btn" onClick="document.location='{{cancelurl}}'" value="{{cancelname}}"/>
<input class="btn btn-success" type="submit" value="{{submitname}}" />
</div>
</form>