minimal edits with a sortable list interface and 'add selected item/in/out support'

This commit is contained in:
j 2013-05-27 20:06:56 +00:00
parent 27877df0cd
commit cfdaaa4464
17 changed files with 1363 additions and 120 deletions

View file

@ -791,6 +791,11 @@
"showMapControls": false, "showMapControls": false,
"showMapLabels": false, "showMapLabels": false,
"showFolder": { "showFolder": {
"edits": {
"personal": true,
"favorite": true,
"featured": true
}
"items": { "items": {
"personal": true, "personal": true,
"favorite": true, "favorite": true,

View file

@ -829,6 +829,11 @@
"showMapControls": false, "showMapControls": false,
"showMapLabels": false, "showMapLabels": false,
"showFolder": { "showFolder": {
"edits": {
"personal": true,
"favorite": true,
"featured": true
},
"items": { "items": {
"personal": true, "personal": true,
"favorite": true, "favorite": true,

View file

@ -713,6 +713,12 @@
"showMapControls": false, "showMapControls": false,
"showMapLabels": false, "showMapLabels": false,
"showFolder": { "showFolder": {
"edits": {
"personal": true,
"favorite": true,
"featured": true,
"volumes": true
},
"items": { "items": {
"personal": true, "personal": true,
"favorite": true, "favorite": true,

View file

@ -632,6 +632,12 @@
"showMapControls": false, "showMapControls": false,
"showMapLabels": false, "showMapLabels": false,
"showFolder": { "showFolder": {
"edits": {
"personal": true,
"favorite": true,
"featured": true,
"volumes": true
},
"items": { "items": {
"personal": true, "personal": true,
"favorite": true, "favorite": true,

133
pandora/edit/managers.py Normal file
View file

@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.db.models import Q, Manager
def parseCondition(condition, user):
'''
'''
k = condition.get('key', 'name')
k = {
'user': 'user__username',
'position': 'position__position',
'posterFrames': 'poster_frames',
}.get(k, k)
if not k:
k = 'name'
v = condition['value']
op = condition.get('operator')
if not op:
op = '='
if op.startswith('!'):
op = op[1:]
exclude = True
else:
exclude = False
if k == 'id':
v = v.split(":")
if len(v) >= 2:
v = (v[0], ":".join(v[1:]))
q = Q(user__username=v[0], name=v[1])
else:
q = Q(id__in=[])
return q
if k == 'subscribed':
key = 'subscribed_users__username'
v = user.username
elif isinstance(v, bool): #featured and public flag
key = k
else:
key = "%s%s" % (k, {
'==': '__iexact',
'^': '__istartswith',
'$': '__iendswith',
}.get(op, '__icontains'))
key = str(key)
if exclude:
q = ~Q(**{key: v})
else:
q = Q(**{key: v})
return q
def parseConditions(conditions, operator, user):
'''
conditions: [
{
value: "war"
}
{
key: "year",
value: "1970-1980,
operator: "!="
},
{
key: "country",
value: "f",
operator: "^"
}
],
operator: "&"
'''
conn = []
for condition in conditions:
if 'conditions' in condition:
q = parseConditions(condition['conditions'],
condition.get('operator', '&'), user)
if q:
conn.append(q)
pass
else:
conn.append(parseCondition(condition, user))
if conn:
q = conn[0]
for c in conn[1:]:
if operator == '|':
q = q | c
else:
q = q & c
return q
return None
class EditManager(Manager):
def get_query_set(self):
return super(EditManager, self).get_query_set()
def find(self, data, user):
'''
query: {
conditions: [
{
value: "war"
}
{
key: "year",
value: "1970-1980,
operator: "!="
},
{
key: "country",
value: "f",
operator: "^"
}
],
operator: "&"
}
'''
#join query with operator
qs = self.get_query_set()
conditions = parseConditions(data['query'].get('conditions', []),
data['query'].get('operator', '&'),
user)
if conditions:
qs = qs.filter(conditions)
if user.is_anonymous():
qs = qs.filter(Q(status='public') | Q(status='featured'))
else:
qs = qs.filter(Q(status='public') | Q(status='featured') | Q(user=user))
return qs

View file

@ -0,0 +1,321 @@
# -*- 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 'Position'
db.create_table('edit_position', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('edit', self.gf('django.db.models.fields.related.ForeignKey')(related_name='position', to=orm['edit.Edit'])),
('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='edit_position', to=orm['auth.User'])),
('section', self.gf('django.db.models.fields.CharField')(max_length='255')),
('position', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('edit', ['Position'])
# Adding unique constraint on 'Position', fields ['user', 'edit', 'section']
db.create_unique('edit_position', ['user_id', 'edit_id', 'section'])
# Deleting field 'Clip.position'
db.delete_column('edit_clip', 'position')
# Deleting field 'Clip.edit_position'
db.delete_column('edit_clip', 'edit_position')
# Adding field 'Clip.index'
db.add_column('edit_clip', 'index',
self.gf('django.db.models.fields.IntegerField')(default=0),
keep_default=False)
# Adding field 'Clip.annotation'
db.add_column('edit_clip', 'annotation',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='editclip', null=True, to=orm['annotation.Annotation']),
keep_default=False)
# Changing field 'Clip.item'
db.alter_column('edit_clip', 'item_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['item.Item']))
# Deleting field 'Edit.public'
db.delete_column('edit_edit', 'public')
# Deleting field 'Edit.duration'
db.delete_column('edit_edit', 'duration')
# Adding field 'Edit.status'
db.add_column('edit_edit', 'status',
self.gf('django.db.models.fields.CharField')(default='private', max_length=20),
keep_default=False)
# Adding field 'Edit.description'
db.add_column('edit_edit', 'description',
self.gf('django.db.models.fields.TextField')(default=''),
keep_default=False)
# Adding field 'Edit.rightslevel'
db.add_column('edit_edit', 'rightslevel',
self.gf('django.db.models.fields.IntegerField')(default=0, db_index=True),
keep_default=False)
# Adding field 'Edit.icon'
db.add_column('edit_edit', 'icon',
self.gf('django.db.models.fields.files.ImageField')(default=None, max_length=100, null=True, blank=True),
keep_default=False)
# Adding field 'Edit.poster_frames'
db.add_column('edit_edit', 'poster_frames',
self.gf('ox.django.fields.TupleField')(default=[]),
keep_default=False)
# Adding M2M table for field subscribed_users on 'Edit'
db.create_table('edit_edit_subscribed_users', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('edit', models.ForeignKey(orm['edit.edit'], null=False)),
('user', models.ForeignKey(orm['auth.user'], null=False))
))
db.create_unique('edit_edit_subscribed_users', ['edit_id', 'user_id'])
def backwards(self, orm):
# Removing unique constraint on 'Position', fields ['user', 'edit', 'section']
db.delete_unique('edit_position', ['user_id', 'edit_id', 'section'])
# Deleting model 'Position'
db.delete_table('edit_position')
# Adding field 'Clip.position'
db.add_column('edit_clip', 'position',
self.gf('django.db.models.fields.IntegerField')(default=0),
keep_default=False)
# Adding field 'Clip.edit_position'
db.add_column('edit_clip', 'edit_position',
self.gf('django.db.models.fields.FloatField')(default=0),
keep_default=False)
# Deleting field 'Clip.index'
db.delete_column('edit_clip', 'index')
# Deleting field 'Clip.annotation'
db.delete_column('edit_clip', 'annotation_id')
# Changing field 'Clip.item'
db.alter_column('edit_clip', 'item_id', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['item.Item']))
# Adding field 'Edit.public'
db.add_column('edit_edit', 'public',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
# Adding field 'Edit.duration'
db.add_column('edit_edit', 'duration',
self.gf('django.db.models.fields.FloatField')(default=0),
keep_default=False)
# Deleting field 'Edit.status'
db.delete_column('edit_edit', 'status')
# Deleting field 'Edit.description'
db.delete_column('edit_edit', 'description')
# Deleting field 'Edit.rightslevel'
db.delete_column('edit_edit', 'rightslevel')
# Deleting field 'Edit.icon'
db.delete_column('edit_edit', 'icon')
# Deleting field 'Edit.poster_frames'
db.delete_column('edit_edit', 'poster_frames')
# Removing M2M table for field subscribed_users on 'Edit'
db.delete_table('edit_edit_subscribed_users')
models = {
'annotation.annotation': {
'Meta': {'object_name': 'Annotation'},
'clip': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotations'", 'null': 'True', 'to': "orm['clip.Clip']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'end': ('django.db.models.fields.FloatField', [], {'default': '-1', 'db_index': 'True'}),
'findvalue': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'item': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotations'", 'to': "orm['item.Item']"}),
'layer': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'public_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'unique': 'True', 'null': 'True'}),
'sortvalue': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1000', 'null': 'True', 'blank': 'True'}),
'start': ('django.db.models.fields.FloatField', [], {'default': '-1', 'db_index': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'value': ('django.db.models.fields.TextField', [], {})
},
'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': '255'}),
'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': '255', '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': '255'}),
'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': '255'})
},
'clip.clip': {
'Meta': {'unique_together': "(('item', 'start', 'end'),)", 'object_name': 'Clip'},
'aspect_ratio': ('django.db.models.fields.FloatField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'duration': ('django.db.models.fields.FloatField', [], {'default': '0', 'db_index': 'True'}),
'end': ('django.db.models.fields.FloatField', [], {'default': '-1'}),
'findvalue': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_index': 'True'}),
'hue': ('django.db.models.fields.FloatField', [], {'default': '0', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'item': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'clips'", 'to': "orm['item.Item']"}),
'keywords': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
'lightness': ('django.db.models.fields.FloatField', [], {'default': '0', 'db_index': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'notes': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
'saturation': ('django.db.models.fields.FloatField', [], {'default': '0', 'db_index': 'True'}),
'sort': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'matching_clips'", 'to': "orm['item.ItemSort']"}),
'sortvalue': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'start': ('django.db.models.fields.FloatField', [], {'default': '-1', 'db_index': 'True'}),
'subtitles': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
'user': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}),
'volume': ('django.db.models.fields.FloatField', [], {'default': '0', 'null': 'True', 'db_index': 'True'})
},
'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'})
},
'edit.clip': {
'Meta': {'object_name': 'Clip'},
'annotation': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'editclip'", 'null': 'True', 'to': "orm['annotation.Annotation']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'edit': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'clips'", 'to': "orm['edit.Edit']"}),
'end': ('django.db.models.fields.FloatField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'index': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'item': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'editclip'", 'null': 'True', 'to': "orm['item.Item']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'start': ('django.db.models.fields.FloatField', [], {'default': '0'})
},
'edit.edit': {
'Meta': {'unique_together': "(('user', 'name'),)", 'object_name': 'Edit'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''"}),
'icon': ('django.db.models.fields.files.ImageField', [], {'default': 'None', 'max_length': '100', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'poster_frames': ('ox.django.fields.TupleField', [], {'default': '[]'}),
'rightslevel': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'private'", 'max_length': '20'}),
'subscribed_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribed_edits'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'edit.position': {
'Meta': {'unique_together': "(('user', 'edit', 'section'),)", 'object_name': 'Position'},
'edit': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'position'", 'to': "orm['edit.Edit']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'section': ('django.db.models.fields.CharField', [], {'max_length': "'255'"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_position'", 'to': "orm['auth.User']"})
},
'item.item': {
'Meta': {'object_name': 'Item'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'data': ('ox.django.fields.DictField', [], {'default': '{}'}),
'external_data': ('ox.django.fields.DictField', [], {'default': '{}'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'items'", 'blank': 'True', 'to': "orm['auth.Group']"}),
'icon': ('django.db.models.fields.files.ImageField', [], {'default': 'None', 'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'itemId': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'blank': 'True'}),
'json': ('ox.django.fields.DictField', [], {'default': '{}'}),
'level': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'oxdbId': ('django.db.models.fields.CharField', [], {'max_length': '42', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'poster': ('django.db.models.fields.files.ImageField', [], {'default': 'None', 'max_length': '100', 'blank': 'True'}),
'poster_frame': ('django.db.models.fields.FloatField', [], {'default': '-1'}),
'poster_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'poster_source': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'poster_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'rendered': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
'stream_aspect': ('django.db.models.fields.FloatField', [], {'default': '1.3333333333333333'}),
'stream_info': ('ox.django.fields.DictField', [], {'default': '{}'}),
'torrent': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '1000', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'null': 'True', 'to': "orm['auth.User']"})
},
'item.itemsort': {
'Meta': {'object_name': 'ItemSort'},
'accessed': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'aspectratio': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'bitrate': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'cinematographer': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'codirector': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'color': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'composer': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'country': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'cutsperminute': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'director': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'duration': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'editor': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'genre': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'height': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'hue': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'imdbId': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'item': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'sort'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['item.Item']"}),
'itemId': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'lightness': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'lyricist': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'numberofactors': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'numberofcuts': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'numberoffiles': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'parts': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'pixels': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'producer': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'productionCompany': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'random': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'resolution': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'rightslevel': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'runtime': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'saturation': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'size': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'sound': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'timesaccessed': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'volume': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'width': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'words': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'wordsperminute': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'writer': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}),
'year': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'db_index': 'True'})
}
}
complete_apps = ['edit']

View file

@ -2,51 +2,318 @@
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
from __future__ import division, with_statement from __future__ import division, with_statement
from django.db import models import re
from django.contrib.auth.models import User import os
import shutil
import ox
from django.conf import settings
from django.db import models
from django.db.models import Max
from django.contrib.auth.models import User
from ox.django.fields import TupleField
from annotation.models import Annotation
from item.models import Item
from archive import extract
import managers
class Edit(models.Model): class Edit(models.Model):
class Meta: class Meta:
unique_together = ("user", "name") unique_together = ("user", "name")
objects = managers.EditManager()
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User) user = models.ForeignKey(User)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
public = models.BooleanField(default=False)
duration = models.FloatField(default=0) status = models.CharField(max_length=20, default='private')
#FIXME: how to deal with width/height? _status = ['private', 'public', 'featured']
description = models.TextField(default='')
rightslevel = models.IntegerField(db_index=True, default=0)
icon = models.ImageField(default=None, blank=True, null=True,
upload_to=lambda i, x: i.path("icon.jpg"))
poster_frames = TupleField(default=[], editable=False)
subscribed_users = models.ManyToManyField(User, related_name='subscribed_edits')
def __unicode__(self): def __unicode__(self):
return u'%s (%s)' % (self.title, self.user) return u'%s (%s)' % (self.title, self.user)
def get_id(self):
return u'%s:%s' % (self.user.username, self.name)
def get_absolute_url(self):
return ('/edits/%s' % quote(self.get_id())).replace('%3A', ':')
def add_clip(self, data):
clip = Clip(edit=self)
if 'annotation' in data:
clip.annotation = Annotation.objects.get(public_id=data['annotation'])
else:
clip.item = Item.objects.get(itemId=data['item'])
clip.start = data['in']
clip.end = data['out']
clip.index = Clip.objects.filter(edit=self).aggregate(Max('index'))['index__max']
if clip.index == None:
clip.index = 0
else:
clip.index +=1
clip.save()
return clip
def accessible(self, user):
return self.user == user or self.status in ('public', 'featured')
def editable(self, user): def editable(self, user):
#FIXME: make permissions work if not user or user.is_anonymous():
if self.user == user or user.is_staff: return False
if self.user == user or \
user.is_staff or \
user.get_profile().capability('canEditFeaturedEdits') == True:
return True return True
return False return False
''' def edit(self, data, user):
#creating a new file from clips seams to work not to bad, needs testing for frame accuracy for key in data:
ffmpeg -i 96p.webm -ss 123.33 -t 3 -vcodec copy -acodec copy 1.webm if key == 'status':
ffmpeg -i 96p.webm -ss 323.33 -t 4 -vcodec copy -acodec copy 2.webm value = data[key]
ffmpeg -i 96p.webm -ss 423.33 -t 1 -vcodec copy -acodec copy 3.webm if value not in self._status:
mkvmerge 1.webm +2.webm +3.webm -o cutup.webm value = self._status[0]
''' if value == 'private':
for user in self.subscribed_users.all():
self.subscribed_users.remove(user)
qs = Position.objects.filter(user=user,
section='section', edit=self)
if qs.count() > 1:
pos = qs[0]
pos.section = 'personal'
pos.save()
elif value == 'featured':
if user.get_profile().capability('canEditFeaturedEdits'):
pos, created = Position.objects.get_or_create(edit=self, user=user,
section='featured')
if created:
qs = Position.objects.filter(user=user, section='featured')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
Position.objects.filter(edit=self).exclude(id=pos.id).delete()
else:
value = self.status
elif self.status == 'featured' and value == 'public':
Position.objects.filter(edit=self).delete()
pos, created = Position.objects.get_or_create(edit=self,
user=self.user,section='personal')
qs = Position.objects.filter(user=self.user,
section='personal')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
for u in self.subscribed_users.all():
pos, created = Position.objects.get_or_create(edit=self, user=u,
section='public')
qs = Position.objects.filter(user=u, section='public')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
self.status = value
elif key == 'name':
data['name'] = re.sub(' \[\d+\]$', '', data['name']).strip()
if not data['name']:
data['name'] = "Untitled"
name = data['name']
num = 1
while Edit.objects.filter(name=name, user=self.user).exclude(id=self.id).count()>0:
num += 1
name = data['name'] + ' [%d]' % num
self.name = name
elif key == 'description':
self.description = ox.sanitize_html(data['description'])
elif key == 'rightslevel':
self.rightslevel = int(data['rightslevel'])
if 'position' in data:
pos, created = Position.objects.get_or_create(edit=self, user=user)
pos.position = data['position']
pos.section = 'featured'
if self.status == 'private':
pos.section = 'personal'
pos.save()
if 'type' in data:
self.type = data['type'] == 'pdf' and 'pdf' or 'html'
if 'posterFrames' in data:
self.poster_frames = tuple(data['posterFrames'])
self.save()
if 'posterFrames' in data:
self.update_icon()
def path(self, name=''):
h = "%07d" % self.id
return os.path.join('edits', h[:2], h[2:4], h[4:6], h[6:], name)
def get_items(self, user=None):
return Item.objects.filter(editclips__id__in=self.clips.all()).distinct()
def update_icon(self):
frames = []
if not self.poster_frames:
items = self.get_items(self.user).filter(rendered=True)
if items.count():
poster_frames = []
for i in range(0, items.count(), max(1, int(items.count()/4))):
poster_frames.append({
'item': items[int(i)].itemId,
'position': items[int(i)].poster_frame
})
self.poster_frames = tuple(poster_frames)
self.save()
for i in self.poster_frames:
s = Item.objects.filter(itemId=i['item'])
if qs.count() > 0:
frame = qs[0].frame(i['position'])
if frame:
frames.append(frame)
self.icon.name = self.path('icon.jpg')
icon = self.icon.path
if frames:
while len(frames) < 4:
frames += frames
folder = os.path.dirname(icon)
ox.makedirs(folder)
for f in glob("%s/icon*.jpg" % folder):
os.unlink(f)
cmd = [
settings.LIST_ICON,
'-f', ','.join(frames),
'-o', icon
]
p = subprocess.Popen(cmd)
p.wait()
self.save()
def get_icon(self, size=16):
path = self.path('icon%d.jpg' % size)
path = os.path.join(settings.MEDIA_ROOT, path)
if not os.path.exists(path):
folder = os.path.dirname(path)
ox.makedirs(folder)
if self.icon and os.path.exists(self.icon.path):
source = self.icon.path
max_size = min(self.icon.width, self.icon.height)
else:
source = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
max_size = 256
if size < max_size:
extract.resize_image(source, path, size=size)
else:
path = source
return path
def json(self, keys=None, user=None):
if not keys:
keys=[
'description',
'editable',
'rightslevel',
'id',
'clips',
'name',
'posterFrames',
'status',
'subscribed',
'user'
]
response = {
'type': 'static'
}
_map = {
'posterFrames': 'poster_frames'
}
for key in keys:
if key == 'id':
response[key] = self.get_id()
elif key == 'clips':
response[key] = [c.json(user) for c in self.clips.all().order_by('index')]
elif key == 'editable':
response[key] = self.editable(user)
elif key == 'user':
response[key] = self.user.username
elif key == 'subscribers':
response[key] = self.subscribed_users.all().count()
elif key == 'subscribed':
if user and not user.is_anonymous():
response[key] = self.subscribed_users.filter(id=user.id).exists()
elif hasattr(self, _map.get(key, key)):
response[key] = getattr(self, _map.get(key,key))
return response
def render(self):
#creating a new file from clips
tmp = tempfile.mkdtemp()
clips = []
for clip in self.clips.all().order_by('index'):
data = clip.json()
clips.append(os.path.join(tmp, '%06d.webm' % data['index']))
cmd = ['avconv', '-i', path,
'-ss', data['in'], '-t', data['out'],
'-vcodec', 'copy', '-acodec', 'copy',
clips[-1]]
#p = subprocess.Popen(cmd)
#p.wait()
cmd = ['mkvmerge', clips[0]] \
+ ['+'+c for c in clips[1:]] \
+ [os.path.join(tmp, 'render.webm')]
#p = subprocess.Popen(cmd)
#p.wait()
shutil.rmtree(tmp)
class Clip(models.Model): class Clip(models.Model):
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
edit = models.ForeignKey(Edit) edit = models.ForeignKey(Edit, related_name='clips')
position = models.IntegerField(default=0) #clip position index = models.IntegerField(default=0)
edit_position = models.FloatField(default=0) #Position in seconds on edit item = models.ForeignKey(Item, null=True, default=None, related_name='editclip')
item = models.ForeignKey("item.Item") annotation = models.ForeignKey(Annotation, null=True, default=None, related_name='editclip')
start = models.FloatField(default=0) start = models.FloatField(default=0)
end = models.FloatField(default=0) end = models.FloatField(default=0)
def __unicode__(self): def __unicode__(self):
if self.annotation:
return u'%s' % self.annotation.public_id
return u'%s/%0.3f-%0.3f' % (self.item.itemId, self.start, self.end) return u'%s/%0.3f-%0.3f' % (self.item.itemId, self.start, self.end)
def json(self, user=None):
data = {
'id': ox.toAZ(self.id),
'index': self.index
}
if self.annotation:
data['annotation'] = self.annotation.public_id
data['in'] = self.annotation.start
data['out'] = self.annotation.end
else:
data['item'] = self.item.itemId
data['in'] = self.start
data['out'] = self.end
data['duration'] = data['out'] - data['in']
return data
class Position(models.Model):
class Meta:
unique_together = ("user", "edit", "section")
edit = models.ForeignKey(Edit, related_name='position')
user = models.ForeignKey(User, related_name='edit_position')
section = models.CharField(max_length='255')
position = models.IntegerField(default=0)
def __unicode__(self):
return u'%s/%s/%s' % (self.section, self.position, self.edit)

View file

@ -1,41 +1,49 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
from __future__ import division from __future__ import division
import os
import re
import ox
from ox.utils import json from ox.utils import json
from ox.django.decorators import login_required_json from ox.django.decorators import login_required_json
from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response
from django.db.models import Max
from ox.django.http import HttpFileResponse
from ox.django.api import actions from ox.django.api import actions
from item import utils
import models import models
def get_edit_or_404_json(id):
id = id.split(':')
username = id[0]
name = ":".join(id[1:])
return get_object_or_404_json(models.Edit, user__username=username, name=name)
@login_required_json @login_required_json
def addClip(request): def addClip(request):
''' '''
takes { takes {
item: string,
edit: string, edit: string,
start: float, item: string,
end: float, in: float,
out: float,
annotation: string
} }
add clip with item/in/out or annotation to edit with id
returns { returns {
} }
''' '''
response = json_response()
data = json.loads(request.POST['data']) data = json.loads(request.POST['data'])
list = get_object_or_404_json(models.Timeline, pk=data['list']) edit = get_edit_or_404_json(data['edit'])
if 'item' in data: if edit.editable(request.user):
item = get_object_or_404_json(models.Item, pk=data['item']) clip = edit.add_clip(data)
if list.editable(request.user): response['data'] = clip.json(request.user)
list.add(item)
response = json_response(status=200, text='item added')
else: else:
response = json_response(status=403, text='not allowed') response = json_response(status=403, text='permission denied')
elif 'query' in data:
response = json_response(status=501, text='not implemented')
else:
response = json_response(status=501, text='not implemented')
return render_to_json_response(response) return render_to_json_response(response)
actions.register(addClip, cache=False) actions.register(addClip, cache=False)
@ -44,125 +52,386 @@ actions.register(addClip, cache=False)
def removeClip(request): def removeClip(request):
''' '''
takes { takes {
item: string edit: string
ids: [string]
} }
returns { returns {
} }
''' '''
response = json_response()
data = json.loads(request.POST['data']) data = json.loads(request.POST['data'])
list = get_object_or_404_json(models.Timeline, pk=data['list']) edit = get_edit_or_404_json(data['edit'])
if 'item' in data: if 'id' in data:
item = get_object_or_404_json(models.Item, pk=data['item']) ids = [data['id']]
if list.editable(request.user):
list.remove(item)
response = json_response(status=200, text='item removed')
else: else:
response = json_response(status=403, text='not allowed') ids = data['ids']
elif 'query' in data: ids = map(ox.fromAZ, ids)
response = json_response(status=501, text='not implemented') if edit.editable(request.user):
for clip in edit.clips.filter(id__in=ids):
clip.delete()
else: else:
response = json_response(status=501, text='not implemented') response = json_response(status=403, text='permission denied')
return render_to_json_response(response) return render_to_json_response(response)
actions.register(removeClip, cache=False) actions.register(removeClip, cache=False)
@login_required_json
def getTimeline(request): def editClip(request):
''' '''
takes { takes {
name: string, id: string,
user: string in: float
}
returns {
}
'''
response = json_response()
data = json.loads(request.POST['data'])
clip = get_object_or_404_json(models.Clip, pk=ox.fromAZ(data['id']))
if clip.edit.editable(request.user):
for key in ('in', 'out'):
if key in data:
if clip.annotation:
clip.start = clip.annotation.start
clip.end = clip.annotation.end
clip.annotation = None
setattr(clip, {'in': 'start', 'out': 'end'}.get(key), float(data[key]))
clip.save()
response['data'] = clip.json(user=request.user)
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
actions.register(editClip, cache=False)
@login_required_json
def sortClips(request):
'''
takes {
edit: string
ids: [string]
}
returns {
}
'''
data = json.loads(request.POST['data'])
edit = get_edit_or_404_json(data['edit'])
response = json_response()
ids = map(ox.fromAZ, data['ids'])
if edit.editable(request.user):
index = 0
for i in ids:
models.Clip.objects.filter(edit=edit, id=i).update(index=index)
index += 1
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
actions.register(sortClips, cache=False)
def getEdit(request):
'''
takes {
id:
}
returns {
id:
clips:
}
'''
data = json.loads(request.POST['data'])
if 'id' in data:
edit = get_edit_or_404_json(data['id'])
response = json_response()
if edit.accessible(request.user):
response['data'] = edit.json(user=request.user)
else:
response = json_response(status=403, text='not allowed')
else:
response = json_response(status=404, text='not found')
return render_to_json_response(response)
actions.register(getEdit)
@login_required_json
def addEdit(request):
'''
takes {
name
} }
returns { returns {
... ...
} }
'''
data = json.loads(request.POST['data'])
data['name'] = re.sub(' \[\d+\]$', '', data.get('name', 'Untitled')).strip()
name = data['name']
if not name:
name = "Untitled"
num = 1
created = False
while not created:
edit, created = models.Edit.objects.get_or_create(name=name, user=request.user)
num += 1
name = data['name'] + ' [%d]' % num
could be del data['name']
timeline: { if data:
0: { edit.edit(data, request.user)
itemId:, start, end else:
}, edit.save()
123: { if edit.status == 'featured':
itemId:, start, end pos, created = models.Position.objects.get_or_create(edit=edit,
user=request.user, section='featured')
qs = models.Position.objects.filter(section='featured')
else:
pos, created = models.Position.objects.get_or_create(edit=edit,
user=request.user, section='personal')
qs = models.Position.objects.filter(user=request.user, section='personal')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
response = json_response(status=200, text='created')
response['data'] = edit.json(user=request.user)
return render_to_json_response(response)
actions.register(addEdit, cache=False)
@login_required_json
def editEdit(request):
'''
takes {
id
} }
returns {
...
} }
or implicit timeline position '''
timeline: [ data = json.loads(request.POST['data'])
edit = get_edit_or_404_json(data['id'])
response = json_response()
if edit.editable(request.user):
edit.edit(data, request.user)
response['data'] = edit.json(keys=[
'description', 'editable', 'rightslevel',
'id', 'name', 'posterFrames', 'status',
'subscribed', 'user'
], user=request.user)
else:
response = json_response(status=403, text='not allowed')
return render_to_json_response(response)
actions.register(editEdit, cache=False)
@login_required_json
def removeEdit(request):
'''
takes {
...
}
returns {
...
}
'''
data = json.loads(request.POST['data'])
edit = get_edit_or_404_json(data['id'])
response = json_response()
if edit.editable(request.user):
edit.delete()
else:
response = json_response(status=403, text='not allowed')
return render_to_json_response(response)
actions.register(removeEdit, cache=False)
def _order_query(qs, sort):
order_by = []
for e in sort:
operator = e['operator']
if operator != '-':
operator = ''
key = {
'subscribed': 'subscribed_users',
'items': 'numberofitems'
}.get(e['key'], e['key'])
order = '%s%s' % (operator, key)
order_by.append(order)
if key == 'subscribers':
qs = qs.annotate(subscribers=Sum('subscribed_users'))
if order_by:
qs = qs.order_by(*order_by)
qs = qs.distinct()
return qs
def parse_query(data, user):
query = {}
query['range'] = [0, 100]
query['sort'] = [{'key':'user', 'operator':'+'}, {'key':'name', 'operator':'+'}]
for key in ('keys', 'group', 'edit', 'range', 'position', 'positions', 'sort'):
if key in data:
query[key] = data[key]
query['qs'] = models.Edit.objects.find(data, user).exclude(name='')
return query
def findEdits(request):
'''
takes {
query: {
conditions: [
{ {
itemId:, start, end key: 'user',
}, value: 'something',
{ operator: '='
itemId:, start, end
} }
] ]
operator: ","
},
sort: [{key: 'name', operator: '+'}],
range: [0, 100]
keys: []
}
''' possible query keys:
response = json_response(status=501, text='not implemented') name, user, featured, subscribed
return render_to_json_response(response)
actions.register(getTimeline)
possible keys:
name, user, featured, subscribed, query
@login_required_json
def addTimeline(request):
'''
takes {
...
} }
returns { returns {
... items: [object]
} }
''' '''
data = json.loads(request.POST['data']) data = json.loads(request.POST['data'])
if models.Timeline.filter(name=data['name'], user=request.user).count() == 0: query = parse_query(data, request.user)
list = models.Timeline(name=data['name'], user=request.user)
list.save() #order
response = json_response(status=200, text='created') is_section_request = query['sort'] == [{u'operator': u'+', u'key': u'position'}]
def is_featured_condition(x):
return x['key'] == 'status' and \
x['value'] == 'featured' and \
x['operator'] in ('=', '==')
is_featured = len(filter(is_featured_condition, data['query'].get('conditions', []))) > 0
if is_section_request:
qs = query['qs']
if not is_featured and not request.user.is_anonymous():
qs = qs.filter(position__in=models.Position.objects.filter(user=request.user))
qs = qs.order_by('position__position')
else: else:
response = json_response(status=200, text='list already exists') qs = _order_query(query['qs'], query['sort'])
response['data']['errors'] = {
'name': 'List already exists' response = json_response()
} if 'keys' in data:
qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [l.json(data['keys'], request.user) for l in qs]
elif 'position' in data:
#FIXME: actually implement position requests
response['data']['position'] = 0
elif 'positions' in data:
ids = [i.get_id() for i in qs]
response['data']['positions'] = utils.get_positions(ids, query['positions'])
else:
response['data']['items'] = qs.count()
return render_to_json_response(response) return render_to_json_response(response)
actions.register(addTimeline, cache=False) actions.register(findEdits)
@login_required_json
def subscribeToEdit(request):
'''
takes {
id: string,
}
returns {}
'''
data = json.loads(request.POST['data'])
edit = get_edit_or_404_json(data['id'])
user = request.user
if edit.status == 'public' and \
edit.subscribed_users.filter(username=user.username).count() == 0:
edit.subscribed_users.add(user)
pos, created = models.Position.objects.get_or_create(edit=edit, user=user, section='public')
if created:
qs = models.Position.objects.filter(user=user, section='public')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
response = json_response()
return render_to_json_response(response)
actions.register(subscribeToEdit, cache=False)
@login_required_json @login_required_json
def editTimeline(request): def unsubscribeFromEdit(request):
''' '''
takes { takes {
... id: string,
} user: username(only admins)
returns {
...
} }
returns {}
''' '''
data = json.loads(request.POST['data']) data = json.loads(request.POST['data'])
list = get_object_or_404_json(models.Timeline, pk=data['list']) edit = get_edit_or_404_json(data['id'])
if list.editable(request.user): user = request.user
for key in data: edit.subscribed_users.remove(user)
if key in ('name', 'public'): models.Position.objects.filter(edit=edit, user=user, section='public').delete()
setattr(list, key, data['key']) response = json_response()
else: return render_to_json_response(response)
actions.register(unsubscribeFromEdit, cache=False)
@login_required_json
def sortEdits(request):
'''
takes {
section: 'personal',
ids: [1,2,4,3]
}
known sections: 'personal', 'public', 'featured'
featured can only be edited by admins
returns {}
'''
data = json.loads(request.POST['data'])
position = 0
section = data['section']
#ids = list(set(data['ids']))
ids = data['ids']
if section == 'featured' and not request.user.get_profile().capability('canEditFeaturedEdits'):
response = json_response(status=403, text='not allowed') response = json_response(status=403, text='not allowed')
return render_to_json_response(response)
actions.register(editTimeline, cache=False)
@login_required_json
def removeTimeline(request):
'''
takes {
...
}
returns {
...
}
'''
data = json.loads(request.POST['data'])
list = get_object_or_404_json(models.Timeline, pk=data['list'])
if list.editable(request.user):
list.delete()
else: else:
response = json_response(status=403, text='not allowed') user = request.user
if section == 'featured':
for i in ids:
l = get_edit_or_404_json(i)
qs = models.Position.objects.filter(section=section, edit=l)
if qs.count() > 0:
pos = qs[0]
else:
pos = models.Position(edit=l, user=user, section=section)
if pos.position != position:
pos.position = position
pos.save()
position += 1
models.Position.objects.filter(section=section, edit=l).exclude(id=pos.id).delete()
else:
for i in ids:
l = get_edit_or_404_json(i)
pos, created = models.Position.objects.get_or_create(edit=l,
user=request.user, section=section)
if pos.position != position:
pos.position = position
pos.save()
position += 1
response = json_response()
return render_to_json_response(response) return render_to_json_response(response)
actions.register(removeTimeline, cache=False) actions.register(sortEdits, cache=False)
def icon(request, id, size=16):
if not size:
size = 16
id = id.split(':')
username = id[0]
name = ":".join(id[1:])
qs = models.Edit.objects.filter(user__username=username, name=name)
if qs.count() == 1 and qs[0].accessible(request.user):
edit = qs[0]
icon = edit.get_icon(int(size))
else:
icon = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
return HttpFileResponse(icon, content_type='image/jpeg')

View file

@ -21,8 +21,8 @@ import models
def get_text_or_404_json(id): def get_text_or_404_json(id):
id = id.split(':') id = id.split(':')
username = id[0] username = id[0]
textname = ":".join(id[1:]) name = ":".join(id[1:])
return get_object_or_404_json(models.Text, user__username=username, name=textname) return get_object_or_404_json(models.Text, user__username=username, name=name)
@login_required_json @login_required_json
def addText(request): def addText(request):

View file

@ -32,6 +32,7 @@ urlpatterns = patterns('',
(r'^resetUI$', 'user.views.reset_ui'), (r'^resetUI$', 'user.views.reset_ui'),
(r'^documents/(?P<id>.*?.pdf).jpg$', 'document.views.thumbnail'), (r'^documents/(?P<id>.*?.pdf).jpg$', 'document.views.thumbnail'),
(r'^documents/(?P<id>.*?.)$', 'document.views.file'), (r'^documents/(?P<id>.*?.)$', 'document.views.file'),
(r'^edit/(?P<id>.*?)/icon(?P<size>\d*).jpg$', 'edit.views.icon'),
(r'^list/(?P<id>.*?)/icon(?P<size>\d*).jpg$', 'itemlist.views.icon'), (r'^list/(?P<id>.*?)/icon(?P<size>\d*).jpg$', 'itemlist.views.icon'),
(r'^text/(?P<id>.*?)/icon(?P<size>\d*).jpg$', 'text.views.icon'), (r'^text/(?P<id>.*?)/icon(?P<size>\d*).jpg$', 'text.views.icon'),
(r'^texts/(?P<id>.*?)/text.pdf$', 'text.views.pdf'), (r'^texts/(?P<id>.*?)/text.pdf$', 'text.views.pdf'),

View file

@ -317,6 +317,20 @@ pandora.URL = (function() {
item: {} item: {}
}; };
// Edits
views['edits'] = {
list: [],
item: ['edit']
};
spanType['edits'] = {
list: [],
item: {edit: 'number'}
};
sortKeys['edits'] = {
list: {},
item: {}
};
findKeys = [{id: 'list', type: 'string'}].concat(pandora.site.itemKeys); findKeys = [{id: 'list', type: 'string'}].concat(pandora.site.itemKeys);
self.URL = Ox.URL({ self.URL = Ox.URL({

View file

@ -0,0 +1,200 @@
'use strict';
pandora.ui.editPanel = function() {
var that = Ox.SplitPanel({
elements: [
{element: Ox.Element(), size: 24},
{element: Ox.Element()},
{element: Ox.Element(), size: 16}
],
orientation: 'vertical'
});
pandora.user.ui.edit && render();
function render() {
pandora.api.getEdit({id: pandora.user.ui.edit}, function(result) {
var edit = result.data;
var $toolbar = Ox.Bar({size: 24}),
$editMenu,
$statusbar = Ox.Bar({size: 16}),
$panel = Ox.SplitPanel({
elements: [
{
element: pandora.$ui.edit = pandora.ui.editList(edit)
},
{
element: Ox.Element(),
size: 0,
resizable: false
}
],
orientation: 'horizontal'
});
that.replaceElement(0, $toolbar);
that.replaceElement(1, $panel);
that.replaceElement(2, $statusbar);
});
}
that.reload = function() {
render();
}
return that;
};
pandora.ui.editList = function(edit) {
var height = getHeight(),
width = getWidth(),
that = Ox.Element()
.css({
'overflow-y': 'auto'
});
self.$list = Ox.TableList({
columns: [
{
align: 'left',
id: 'index',
operator: '+',
title: Ox._('Index'),
visible: false,
width: 60
},
{
align: 'left',
id: 'id',
operator: '+',
title: Ox._('ID'),
visible: false,
width: 60
},
{
align: 'left',
id: 'item',
operator: '+',
title: Ox._(pandora.site.itemName.singular),
visible: true,
width: 360
},
{
editable: true,
id: 'in',
operator: '+',
title: Ox._('In'),
visible: true,
width: 60
},
{
editable: true,
id: 'out',
operator: '+',
title: Ox._('Out'),
visible: true,
width: 60
},
{
id: 'duration',
operator: '+',
title: Ox._('Duration'),
visible: true,
width: 60
}
],
columnsMovable: true,
columnsRemovable: true,
columnsResizable: true,
columnsVisible: true,
items: edit.clips,
scrollbarVisible: true,
sort: [{key: 'index', operator: '+'}],
sortable: true,
unique: 'id'
})
.appendTo(that)
.bindEvent({
add: function(data) {
if(pandora.user.ui.item) {
pandora.api.addClip({
edit: pandora.user.ui.edit,
item: pandora.user.ui.item,
'in': pandora.user.ui.videoPoints[pandora.user.ui.item]['in'],
out: pandora.user.ui.videoPoints[pandora.user.ui.item].out,
}, function(result) {
Ox.Request.clearCache();
pandora.$ui.rightPanel.reload()
});
}
},
'delete': function(data) {
if (data.ids.length > 0 && edit.editable) {
pandora.api.removeClip({
ids: data.ids,
edit: pandora.user.ui.edit
}, function(result) {
Ox.Request.clearCache();
pandora.$ui.rightPanel.reload()
});
}
},
move: function(data) {
Ox.Request.clearCache();
pandora.api.sortClips({
edit: pandora.user.ui.edit,
ids: data.ids
})
},
select: function(data) {
},
submit: function(data) {
var value = self.$list.value(data.id, data.key);
if (data.value != value && !(data.value === '' && value === null)) {
self.$list.value(data.id, data.key, data.value || null);
var edit = {
id: data.id,
};
edit[data.key] = parseFloat(data.value);
pandora.api.editClip(edit, function(result) {
self.$list.value(data.id, data.key, result.data[data.key]);
self.$list.value(data.id, 'duration', result.data.duration);
});
}
}
});
function getHeight() {
// 24 menu + 24 toolbar + 16 statusbar + 32 title + 32 margins
// + 1px to ge trid of scrollbar
return window.innerHeight - 128 -1;
}
function getWidth() {
return window.innerWidth
- pandora.user.ui.showSidebar * pandora.user.ui.sidebarSize - 1
- pandora.user.ui.embedSize - 1
- 32;
}
that.update = function() {
$text.options({
maxHeight: getHeight(),
width: getWidth()
});
return that;
};
return that;
};

View file

@ -20,6 +20,9 @@ pandora.ui.mainPanel = function() {
orientation: 'horizontal' orientation: 'horizontal'
}) })
.bindEvent({ .bindEvent({
pandora_edit: function(data) {
that.replaceElement(1, pandora.$ui.rightPanel = pandora.ui.rightPanel());
},
pandora_find: function() { pandora_find: function() {
var previousUI = pandora.UI.getPrevious(); var previousUI = pandora.UI.getPrevious();
Ox.Log('FIND', 'handled in mainPanel', previousUI.item, previousUI._list) Ox.Log('FIND', 'handled in mainPanel', previousUI.item, previousUI._list)

View file

@ -59,6 +59,8 @@ pandora.ui.rightPanel = function() {
}); });
} else if (pandora.user.ui.section == 'texts') { } else if (pandora.user.ui.section == 'texts') {
that = pandora.$ui.textPanel = pandora.ui.textPanel(); that = pandora.$ui.textPanel = pandora.ui.textPanel();
} else if (pandora.user.ui.section == 'edits') {
that = pandora.$ui.editPanel = pandora.ui.editPanel();
} }
return that; return that;
}; };

View file

@ -3,8 +3,8 @@
pandora.ui.sectionButtons = function() { pandora.ui.sectionButtons = function() {
var that = Ox.ButtonGroup({ var that = Ox.ButtonGroup({
buttons: [ buttons: [
{id: 'items', title: pandora.site.itemName.plural}, {id: 'items', title: Ox._(pandora.site.itemName.plural)},
{id: 'edits', title: Ox._('Edits'), disabled: true}, {id: 'edits', title: Ox._('Edits'), disabled: pandora.user.level != 'admin'},
{id: 'texts', title: Ox._('Texts'), disabled: pandora.user.level != 'admin'} {id: 'texts', title: Ox._('Texts'), disabled: pandora.user.level != 'admin'}
], ],
id: 'sectionButtons', id: 'sectionButtons',

View file

@ -5,9 +5,9 @@ pandora.ui.sectionSelect = function() {
var that = Ox.Select({ var that = Ox.Select({
id: 'sectionSelect', id: 'sectionSelect',
items: [ items: [
{id: 'items', title: pandora.site.itemName.plural}, {id: 'items', title: Ox._(pandora.site.itemName.plural)},
{id: 'edits', title: Ox._('Edits'), disabled: true}, {id: 'edits', title: Ox._('Edits'), disabled: pandora.user.level != 'admin'}
{id: 'texts', title: Ox._('Texts'), disabled: true} {id: 'texts', title: Ox._('Texts'), disabled: pandora.user.level != 'admin'}
], ],
value: pandora.user.ui.section value: pandora.user.ui.section
}).css({ }).css({

View file

@ -785,6 +785,16 @@ pandora.getItem = function(state, str, callback) {
callback(); callback();
} }
}); });
} else if (state.type == 'edits') {
pandora.api.getEdit({id: str}, function(result) {
if (result.status.code == 200) {
state.item = result.data.id;
callback();
} else {
state.item = '';
callback();
}
});
} else { } else {
callback(); callback();
} }
@ -1444,9 +1454,10 @@ pandora.selectList = function() {
}); });
} }
} else { } else {
var id = pandora.user.ui[pandora.user.ui.section.slice(0,-1)]; var id = pandora.user.ui[pandora.user.ui.section.slice(0,-1)],
section = Ox.toTitleCase(pandora.user.ui.section.slice(0, -1));
if (id) { if (id) {
pandora.api.getText({id: id}, function(result) { pandora.api['edit' + section]({id: id}, function(result) {
var folder; var folder;
if (result.data.id) { if (result.data.id) {
folder = result.data.status == 'featured' ? 'featured' : ( folder = result.data.status == 'featured' ? 'featured' : (