minimal edits with a sortable list interface and 'add selected item/in/out support'
This commit is contained in:
parent
27877df0cd
commit
cfdaaa4464
17 changed files with 1363 additions and 120 deletions
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
133
pandora/edit/managers.py
Normal 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
|
||||||
|
|
||||||
|
|
321
pandora/edit/migrations/0002_cleanup.py
Normal file
321
pandora/edit/migrations/0002_cleanup.py
Normal 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']
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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:
|
|
||||||
response = json_response(status=403, text='not allowed')
|
|
||||||
elif 'query' in data:
|
|
||||||
response = json_response(status=501, text='not implemented')
|
|
||||||
|
|
||||||
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(addClip, cache=False)
|
actions.register(addClip, cache=False)
|
||||||
|
|
||||||
|
@ -44,112 +52,172 @@ 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:
|
|
||||||
response = json_response(status=403, text='not allowed')
|
|
||||||
elif 'query' in data:
|
|
||||||
response = json_response(status=501, text='not implemented')
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
response = json_response(status=501, text='not implemented')
|
ids = data['ids']
|
||||||
|
ids = map(ox.fromAZ, ids)
|
||||||
|
if edit.editable(request.user):
|
||||||
|
for clip in edit.clips.filter(id__in=ids):
|
||||||
|
clip.delete()
|
||||||
|
else:
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
def getTimeline(request):
|
|
||||||
'''
|
|
||||||
takes {
|
|
||||||
name: string,
|
|
||||||
user: string
|
|
||||||
}
|
|
||||||
returns {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
could be
|
|
||||||
timeline: {
|
|
||||||
0: {
|
|
||||||
itemId:, start, end
|
|
||||||
},
|
|
||||||
123: {
|
|
||||||
itemId:, start, end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
or implicit timeline position
|
|
||||||
timeline: [
|
|
||||||
{
|
|
||||||
itemId:, start, end
|
|
||||||
},
|
|
||||||
{
|
|
||||||
itemId:, start, end
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
'''
|
|
||||||
response = json_response(status=501, text='not implemented')
|
|
||||||
return render_to_json_response(response)
|
|
||||||
actions.register(getTimeline)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required_json
|
@login_required_json
|
||||||
def addTimeline(request):
|
def editClip(request):
|
||||||
'''
|
'''
|
||||||
takes {
|
takes {
|
||||||
...
|
id: string,
|
||||||
|
in: float
|
||||||
}
|
}
|
||||||
returns {
|
returns {
|
||||||
...
|
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
response = json_response()
|
||||||
data = json.loads(request.POST['data'])
|
data = json.loads(request.POST['data'])
|
||||||
if models.Timeline.filter(name=data['name'], user=request.user).count() == 0:
|
clip = get_object_or_404_json(models.Clip, pk=ox.fromAZ(data['id']))
|
||||||
list = models.Timeline(name=data['name'], user=request.user)
|
if clip.edit.editable(request.user):
|
||||||
list.save()
|
for key in ('in', 'out'):
|
||||||
response = json_response(status=200, text='created')
|
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:
|
else:
|
||||||
response = json_response(status=200, text='list already exists')
|
response = json_response(status=403, text='permission denied')
|
||||||
response['data']['errors'] = {
|
|
||||||
'name': 'List already exists'
|
|
||||||
}
|
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
actions.register(addTimeline, cache=False)
|
actions.register(editClip, cache=False)
|
||||||
|
|
||||||
|
|
||||||
@login_required_json
|
@login_required_json
|
||||||
def editTimeline(request):
|
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 {
|
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 = json.loads(request.POST['data'])
|
||||||
list = get_object_or_404_json(models.Timeline, pk=data['list'])
|
data['name'] = re.sub(' \[\d+\]$', '', data.get('name', 'Untitled')).strip()
|
||||||
if list.editable(request.user):
|
name = data['name']
|
||||||
for key in data:
|
if not name:
|
||||||
if key in ('name', 'public'):
|
name = "Untitled"
|
||||||
setattr(list, key, data['key'])
|
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
|
||||||
|
|
||||||
|
del data['name']
|
||||||
|
if data:
|
||||||
|
edit.edit(data, request.user)
|
||||||
|
else:
|
||||||
|
edit.save()
|
||||||
|
if edit.status == 'featured':
|
||||||
|
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 {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
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:
|
else:
|
||||||
response = json_response(status=403, text='not allowed')
|
response = json_response(status=403, text='not allowed')
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
actions.register(editTimeline, cache=False)
|
actions.register(editEdit, cache=False)
|
||||||
|
|
||||||
|
|
||||||
@login_required_json
|
@login_required_json
|
||||||
def removeTimeline(request):
|
def removeEdit(request):
|
||||||
'''
|
'''
|
||||||
takes {
|
takes {
|
||||||
...
|
...
|
||||||
|
@ -159,10 +227,211 @@ def removeTimeline(request):
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
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):
|
response = json_response()
|
||||||
list.delete()
|
if edit.editable(request.user):
|
||||||
|
edit.delete()
|
||||||
else:
|
else:
|
||||||
response = json_response(status=403, text='not allowed')
|
response = json_response(status=403, text='not allowed')
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
actions.register(removeTimeline, cache=False)
|
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: [
|
||||||
|
{
|
||||||
|
key: 'user',
|
||||||
|
value: 'something',
|
||||||
|
operator: '='
|
||||||
|
}
|
||||||
|
]
|
||||||
|
operator: ","
|
||||||
|
},
|
||||||
|
sort: [{key: 'name', operator: '+'}],
|
||||||
|
range: [0, 100]
|
||||||
|
keys: []
|
||||||
|
}
|
||||||
|
|
||||||
|
possible query keys:
|
||||||
|
name, user, featured, subscribed
|
||||||
|
|
||||||
|
possible keys:
|
||||||
|
name, user, featured, subscribed, query
|
||||||
|
|
||||||
|
}
|
||||||
|
returns {
|
||||||
|
items: [object]
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
query = parse_query(data, request.user)
|
||||||
|
|
||||||
|
#order
|
||||||
|
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:
|
||||||
|
qs = _order_query(query['qs'], query['sort'])
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
def unsubscribeFromEdit(request):
|
||||||
|
'''
|
||||||
|
takes {
|
||||||
|
id: string,
|
||||||
|
user: username(only admins)
|
||||||
|
}
|
||||||
|
returns {}
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
edit = get_edit_or_404_json(data['id'])
|
||||||
|
user = request.user
|
||||||
|
edit.subscribed_users.remove(user)
|
||||||
|
models.Position.objects.filter(edit=edit, user=user, section='public').delete()
|
||||||
|
response = json_response()
|
||||||
|
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')
|
||||||
|
else:
|
||||||
|
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)
|
||||||
|
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')
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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({
|
||||||
|
|
200
static/js/pandora/editPanel.js
Normal file
200
static/js/pandora/editPanel.js
Normal 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;
|
||||||
|
|
||||||
|
};
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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' : (
|
||||||
|
|
Loading…
Reference in a new issue