From 8f6f63c87128906be86fdfdf53aba48570677e87 Mon Sep 17 00:00:00 2001 From: Leif Johansson Date: Tue, 16 Oct 2012 17:57:45 +0200 Subject: - normalize sco objects to separate table - add archive object for "published" archives - configurable return from form edits - tagging for archives - reset south --- meetingtools/apps/archive/__init__.py | 1 + meetingtools/apps/archive/admin.py | 10 + meetingtools/apps/archive/forms.py | 6 + meetingtools/apps/archive/management/__init__.py | 1 + .../apps/archive/management/commands/__init__.py | 1 + .../management/commands/update_recordings.py | 19 ++ meetingtools/apps/archive/models.py | 50 ++++ meetingtools/apps/archive/views.py | 63 +++++ meetingtools/apps/room/forms.py | 4 +- meetingtools/apps/room/migrations/0001_initial.py | 122 +++++++++ meetingtools/apps/room/migrations/__init__.py | 0 meetingtools/apps/room/models.py | 38 ++- meetingtools/apps/room/tasks.py | 23 +- meetingtools/apps/room/views.py | 273 ++++++++++----------- meetingtools/apps/sco/__init__.py | 1 + meetingtools/apps/sco/admin.py | 10 + meetingtools/apps/sco/models.py | 98 ++++++++ meetingtools/settings.py | 6 +- meetingtools/urls.py | 3 + templates/apps/archive/tag.html | 32 +++ templates/apps/room/recordings.html | 25 +- templates/edit.html | 2 +- 22 files changed, 606 insertions(+), 182 deletions(-) create mode 100644 meetingtools/apps/archive/__init__.py create mode 100644 meetingtools/apps/archive/admin.py create mode 100644 meetingtools/apps/archive/forms.py create mode 100644 meetingtools/apps/archive/management/__init__.py create mode 100644 meetingtools/apps/archive/management/commands/__init__.py create mode 100644 meetingtools/apps/archive/management/commands/update_recordings.py create mode 100644 meetingtools/apps/archive/models.py create mode 100644 meetingtools/apps/archive/views.py create mode 100644 meetingtools/apps/room/migrations/0001_initial.py create mode 100644 meetingtools/apps/room/migrations/__init__.py create mode 100644 meetingtools/apps/sco/__init__.py create mode 100644 meetingtools/apps/sco/admin.py create mode 100644 meetingtools/apps/sco/models.py create mode 100644 templates/apps/archive/tag.html 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 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 %} +
+
+ + Note Well 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 not mean that anyone can access + your recording. +
+ {% if tags %} +
+ {% for tag in tags %} + + {{tag}}  + + + {% endfor %} +
+ {% else %} +
There are no tags yet...
+
+ {% 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 @@
{% for r in recordings %}
- -
+ +
+

{{r.url}}

{% if r.description %}{{r.description|safe}}{% else %}No description available...{% endif %}
  • » Created {{r.date_created|datehumanize}}.
  • » Modified {{r.date_modified|datehumanize}}.
  • -
  • » Hosted on {{room.acc.name}}
  • -
+
  • » Hosted on {{room.sco.acc.name}}
  • + {% if r.published %} + {% tags_for_object r.ar as tags %} + {% if tags %}
  • » {% for tag in tags %}{{tag}}{% endfor %}{%if edit %} manage archive tags{%endif%}
  • {% endif %} + {% endif %} + +
    - Play Recording - Download Recording + Play + Download + {% if not r.published %} + Publish + {% else %} + Manage Tags + {% endif %}
    @@ -40,7 +51,7 @@

    No recordings right now...

    {% endif %}
    - Back to '{{room.name}}' + Back to '{{room.name}}' {% endblock %} {% block validators %} [Valid Atom 1.0] 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 %}
    - +
    -- cgit v1.1