split models into apps, new api interface
This commit is contained in:
parent
f833109c02
commit
b8e5764f3d
32 changed files with 2033 additions and 488 deletions
7
README
7
README
|
@ -27,6 +27,13 @@ Install rabbitmq and carrot:
|
||||||
|
|
||||||
update BROKER_* settings in local_settings.py:
|
update BROKER_* settings in local_settings.py:
|
||||||
|
|
||||||
|
get current oxjs
|
||||||
|
cd static
|
||||||
|
bzr branch http://code.0x2620.org/oxjs
|
||||||
|
|
||||||
|
Database:
|
||||||
|
with postresql install python-psycopg2
|
||||||
|
|
||||||
Development:
|
Development:
|
||||||
we are using django, http://docs.djangoproject.com/en/dev/
|
we are using django, http://docs.djangoproject.com/en/dev/
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,7 @@ from django.shortcuts import render_to_response, get_object_or_404, get_list_or_
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
try:
|
from ox.utils import json
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
from django.utils import simplejson as 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 ox.django.http import HttpFileResponse
|
from ox.django.http import HttpFileResponse
|
||||||
|
@ -31,15 +27,18 @@ import models
|
||||||
import utils
|
import utils
|
||||||
import tasks
|
import tasks
|
||||||
|
|
||||||
from user.models import getUserJSON
|
from pandora.user.models import getUserJSON
|
||||||
from user.views import api_login, api_logout, api_register, api_contact, api_recover, api_preferences, api_findUser
|
from pandora.user.views import api_login, api_logout, api_register, api_contact, api_recover, api_preferences, api_findUser
|
||||||
|
|
||||||
from archive.views import api_update, api_upload, api_editFile, api_encodingProfile
|
from pandora.archive.views import api_update, api_upload, api_editFile, api_encodingProfile
|
||||||
|
|
||||||
from archive.models import File
|
from pandora.archive.models import File
|
||||||
from archive import extract
|
from pandora.archive import extract
|
||||||
|
|
||||||
from item.views import *
|
from pandora.item.views import *
|
||||||
|
from pandora.itemlist.views import *
|
||||||
|
from pandora.place.views import *
|
||||||
|
from pandora.date.views import *
|
||||||
|
|
||||||
def api(request):
|
def api(request):
|
||||||
if request.META['REQUEST_METHOD'] == "OPTIONS":
|
if request.META['REQUEST_METHOD'] == "OPTIONS":
|
||||||
|
@ -73,6 +72,18 @@ def api_api(request):
|
||||||
actions.sort()
|
actions.sort()
|
||||||
return render_to_json_response(json_response({'actions': actions}))
|
return render_to_json_response(json_response({'actions': actions}))
|
||||||
|
|
||||||
|
def api_apidoc(request):
|
||||||
|
'''
|
||||||
|
returns array of actions with documentation
|
||||||
|
'''
|
||||||
|
actions = globals().keys()
|
||||||
|
actions = map(lambda a: a[4:], filter(lambda a: a.startswith('api_'), actions))
|
||||||
|
actions.sort()
|
||||||
|
docs = {}
|
||||||
|
for f in actions:
|
||||||
|
docs[f] = get_api_doc(f)
|
||||||
|
return render_to_json_response(json_response({'actions': docs}))
|
||||||
|
|
||||||
def api_hello(request):
|
def api_hello(request):
|
||||||
'''
|
'''
|
||||||
return {'status': {'code': int, 'text': string},
|
return {'status': {'code': int, 'text': string},
|
||||||
|
@ -93,10 +104,9 @@ def api_error(request):
|
||||||
success = error_is_success
|
success = error_is_success
|
||||||
return render_to_json_response({})
|
return render_to_json_response({})
|
||||||
|
|
||||||
def apidoc(request):
|
def get_api_doc(f):
|
||||||
'''
|
f = 'api_' + f
|
||||||
this is used for online documentation at http://127.0.0.1:8000/api/
|
|
||||||
'''
|
|
||||||
import sys
|
import sys
|
||||||
def trim(docstring):
|
def trim(docstring):
|
||||||
if not docstring:
|
if not docstring:
|
||||||
|
@ -123,12 +133,19 @@ def apidoc(request):
|
||||||
# Return a single string:
|
# Return a single string:
|
||||||
return '\n'.join(trimmed)
|
return '\n'.join(trimmed)
|
||||||
|
|
||||||
|
return trim(globals()[f].__doc__)
|
||||||
|
|
||||||
|
def apidoc(request):
|
||||||
|
'''
|
||||||
|
this is used for online documentation at http://127.0.0.1:8000/api/
|
||||||
|
'''
|
||||||
|
|
||||||
functions = filter(lambda x: x.startswith('api_'), globals().keys())
|
functions = filter(lambda x: x.startswith('api_'), globals().keys())
|
||||||
api = []
|
api = []
|
||||||
for f in sorted(functions):
|
for f in sorted(functions):
|
||||||
api.append({
|
api.append({
|
||||||
'name': f[4:],
|
'name': f[4:],
|
||||||
'doc': trim(globals()[f].__doc__).replace('\n', '<br>\n')
|
'doc': get_api_doc(f[4:]).replace('\n', '<br>\n')
|
||||||
})
|
})
|
||||||
context = RequestContext(request, {'api': api,
|
context = RequestContext(request, {'api': api,
|
||||||
'sitename': settings.SITENAME,})
|
'sitename': settings.SITENAME,})
|
||||||
|
|
0
pandora/date/__init__.py
Normal file
0
pandora/date/__init__.py
Normal file
12
pandora/date/admin.py
Normal file
12
pandora/date/admin.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
|
||||||
|
class DateAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['name']
|
||||||
|
admin.site.register(models.Date, DateAdmin)
|
||||||
|
|
19
pandora/date/managers.py
Normal file
19
pandora/date/managers.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ox.utils import json
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.models import Q, Manager
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
class DateManager(Manager):
|
||||||
|
def get_query_set(self):
|
||||||
|
return super(DateManager, self).get_query_set()
|
||||||
|
|
||||||
|
def find(self, q=''):
|
||||||
|
qs = self.get_query_set()
|
||||||
|
qs = qs.filter(Q(name_find__icontains=q))
|
||||||
|
return qs
|
44
pandora/date/models.py
Normal file
44
pandora/date/models.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division, with_statement
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.django import fields
|
||||||
|
|
||||||
|
import managers
|
||||||
|
|
||||||
|
class Date(models.Model):
|
||||||
|
'''
|
||||||
|
Dates are dates in time that can be once or recurring,
|
||||||
|
From Mondays to Spring to 1989 to Roman Empire
|
||||||
|
'''
|
||||||
|
name = models.CharField(null=True, max_length=255, unique=True)
|
||||||
|
name_sort = models.CharField(null=True, max_length=255, unique=True)
|
||||||
|
name_find = models.TextField(default='', editable=True)
|
||||||
|
wikipediaId = models.CharField(max_length=1000, blank=True)
|
||||||
|
|
||||||
|
objects = managers.DateManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name_sort', )
|
||||||
|
|
||||||
|
#FIXME: how to deal with aliases
|
||||||
|
aliases = fields.TupleField(default=[])
|
||||||
|
|
||||||
|
#once|year|week|day
|
||||||
|
recurring = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
#start yyyy-mm-dd|mm-dd|dow 00:00|00:00
|
||||||
|
#end yyyy-mm-dd|mm-dd|dow 00:00|00:01
|
||||||
|
start = models.CharField(null=True, max_length=255)
|
||||||
|
end = models.CharField(null=True, max_length=255)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.name_sort:
|
||||||
|
self.name_sort = self.name
|
||||||
|
self.name_find = self.name + '||'.join(self.aliases)
|
||||||
|
super(Date, self).save(*args, **kwargs)
|
||||||
|
|
23
pandora/date/tests.py
Normal file
23
pandora/date/tests.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
This file demonstrates two different styles of tests (one doctest and one
|
||||||
|
unittest). These will both pass when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace these with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
class SimpleTest(TestCase):
|
||||||
|
def test_basic_addition(self):
|
||||||
|
"""
|
||||||
|
Tests that 1 + 1 always equals 2.
|
||||||
|
"""
|
||||||
|
self.failUnlessEqual(1 + 1, 2)
|
||||||
|
|
||||||
|
__test__ = {"doctest": """
|
||||||
|
Another way to test that 1 + 1 is equal to 2.
|
||||||
|
|
||||||
|
>>> 1 + 1 == 2
|
||||||
|
True
|
||||||
|
"""}
|
||||||
|
|
113
pandora/date/views.py
Normal file
113
pandora/date/views.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from urllib2 import unquote
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db.models import Q, Avg, Count, Sum
|
||||||
|
from django.http import HttpResponse, Http404
|
||||||
|
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404, redirect
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.utils import 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.http import HttpFileResponse
|
||||||
|
import ox
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def api_addDate(request):
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
if models.Date.filter(name=data['name']).count() == 0:
|
||||||
|
place = models.Date(name = data['name'])
|
||||||
|
place.save()
|
||||||
|
response = json_response(status=200, text='created')
|
||||||
|
else:
|
||||||
|
response = json_response(status=403, text='place name exists')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def api_editDate(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
{
|
||||||
|
'id': dateid,
|
||||||
|
'date': dict
|
||||||
|
}
|
||||||
|
date contains key/value pairs with place propterties
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
Date = get_object_or_404_json(models.Date, pk=data['id'])
|
||||||
|
if Date.editable(request.user):
|
||||||
|
conflict = False
|
||||||
|
names = [data['date']['name']] + data['date']['aliases']
|
||||||
|
for name in names: #FIXME: also check aliases!
|
||||||
|
if models.Date.filter(name=data['name']).exclude(id=Date.id).count() != 0:
|
||||||
|
conflict = True
|
||||||
|
if not conflict:
|
||||||
|
for key in data['date']:
|
||||||
|
setattr(Date, key, data['date'][key])
|
||||||
|
Date.save()
|
||||||
|
response = json_response(status=200, text='updated')
|
||||||
|
else:
|
||||||
|
response = json_response(status=403, text='Date name/alias conflict')
|
||||||
|
else:
|
||||||
|
response = json_response(status=403, text='permission denied')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def api_removeDate(request):
|
||||||
|
response = json_response(status=501, text='not implemented')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
def api_findDate(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
{'query': query, 'sort': array, 'range': array}
|
||||||
|
|
||||||
|
query: query object, more on query syntax at
|
||||||
|
https://wiki.0x2620.org/wiki/pandora/QuerySyntax
|
||||||
|
sort: array of key, operator dics
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "year",
|
||||||
|
operator: "-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "director",
|
||||||
|
operator: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
range: result range, array [from, to]
|
||||||
|
|
||||||
|
with keys, items is list of dicts with requested properties:
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {items: array}}
|
||||||
|
|
||||||
|
Positions
|
||||||
|
param data
|
||||||
|
{'query': query, 'ids': []}
|
||||||
|
|
||||||
|
query: query object, more on query syntax at
|
||||||
|
https://wiki.0x2620.org/wiki/pandora/QuerySyntax
|
||||||
|
ids: ids of dates for which positions are required
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
response = json_response(status=200, text='ok')
|
||||||
|
response['data']['places'] = []
|
||||||
|
#FIXME: add coordinates to limit search
|
||||||
|
for p in Dates.objects.find(data['query']):
|
||||||
|
response['data']['dates'].append(p.json())
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
970
pandora/fixtures/0xdb_properties.json
Normal file
970
pandora/fixtures/0xdb_properties.json
Normal file
|
@ -0,0 +1,970 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "title",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "title",
|
||||||
|
"title": "Title",
|
||||||
|
"default": true,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 0,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 10,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": true,
|
||||||
|
"name": "director",
|
||||||
|
"title": "Director",
|
||||||
|
"default": true,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 1,
|
||||||
|
"array": true,
|
||||||
|
"type": "person",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 28,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "sring",
|
||||||
|
"admin": false,
|
||||||
|
"group": true,
|
||||||
|
"name": "country",
|
||||||
|
"title": "Country",
|
||||||
|
"default": true,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 120,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 2,
|
||||||
|
"array": true,
|
||||||
|
"type": "string",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 15,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": true,
|
||||||
|
"name": "year",
|
||||||
|
"title": "Year",
|
||||||
|
"default": true,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 3,
|
||||||
|
"array": false,
|
||||||
|
"type": "string",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 26,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": true,
|
||||||
|
"name": "language",
|
||||||
|
"title": "Language",
|
||||||
|
"default": true,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 120,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 4,
|
||||||
|
"array": true,
|
||||||
|
"type": "string",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 37,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "integer",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "runtime",
|
||||||
|
"title": "Runtime",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": true,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 5,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 29,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "writer",
|
||||||
|
"title": "Writer",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 6,
|
||||||
|
"array": true,
|
||||||
|
"type": "person",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 6,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "producer",
|
||||||
|
"title": "Producer",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 7,
|
||||||
|
"array": true,
|
||||||
|
"type": "person",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 13,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "cinematographer",
|
||||||
|
"title": "Cinematographer",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 8,
|
||||||
|
"array": true,
|
||||||
|
"type": "person",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 34,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "editor",
|
||||||
|
"title": "Editor",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 9,
|
||||||
|
"array": true,
|
||||||
|
"type": "person",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 33,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "length",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "actors",
|
||||||
|
"title": "Actors",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 10,
|
||||||
|
"array": true,
|
||||||
|
"type": "role",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 46,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "name",
|
||||||
|
"title": "Name",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 11,
|
||||||
|
"array": true,
|
||||||
|
"type": "name",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 45,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "character",
|
||||||
|
"title": "Character",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 11,
|
||||||
|
"array": true,
|
||||||
|
"type": "character",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 16,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "length",
|
||||||
|
"admin": false,
|
||||||
|
"group": true,
|
||||||
|
"name": "genre",
|
||||||
|
"title": "Genre",
|
||||||
|
"default": true,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": true,
|
||||||
|
"width": 120,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 11,
|
||||||
|
"array": true,
|
||||||
|
"type": "string",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 11,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "length",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "keywords",
|
||||||
|
"title": "Keywords",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 12,
|
||||||
|
"array": true,
|
||||||
|
"type": "string",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 31,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "length",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "summary",
|
||||||
|
"title": "Summary",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 13,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 25,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "length",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "trivia",
|
||||||
|
"title": "Trivia",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 14,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 32,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "date",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "releasedate",
|
||||||
|
"title": "Releasedate",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 15,
|
||||||
|
"array": false,
|
||||||
|
"type": "date",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 30,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "float",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "budget",
|
||||||
|
"title": "Budget",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 16,
|
||||||
|
"array": false,
|
||||||
|
"type": "float",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 23,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "float",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "gross",
|
||||||
|
"title": "Gross",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 17,
|
||||||
|
"array": false,
|
||||||
|
"type": "float",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 27,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "float",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "profit",
|
||||||
|
"title": "Profit",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 18,
|
||||||
|
"array": false,
|
||||||
|
"type": "float",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 3,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "integer",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "rating",
|
||||||
|
"title": "Rating",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 19,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 24,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "integer",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "votes",
|
||||||
|
"title": "Votes",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 20,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 17,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "float",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "aspectratio",
|
||||||
|
"title": "Aspectratio",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 21,
|
||||||
|
"array": false,
|
||||||
|
"type": "faction",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 8,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "float",
|
||||||
|
"admin": true,
|
||||||
|
"group": false,
|
||||||
|
"name": "duration",
|
||||||
|
"title": "Duration",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": true,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 22,
|
||||||
|
"array": false,
|
||||||
|
"type": "float",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 7,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "color",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "color",
|
||||||
|
"title": "Color",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 23,
|
||||||
|
"array": false,
|
||||||
|
"type": "color",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 4,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "integer",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "saturation",
|
||||||
|
"title": "Saturation",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 24,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 12,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "integer",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "brightness",
|
||||||
|
"title": "Brightness",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 25,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 22,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "integer",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "volume",
|
||||||
|
"title": "Volume",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 26,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 41,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": null,
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "clips",
|
||||||
|
"title": "Clips",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 27,
|
||||||
|
"array": false,
|
||||||
|
"type": null,
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 42,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": null,
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "cuts",
|
||||||
|
"title": "Cuts",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 28,
|
||||||
|
"array": false,
|
||||||
|
"type": null,
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 39,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "cutsperminute",
|
||||||
|
"title": "Cuts per minute",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 29,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 14,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "words",
|
||||||
|
"title": "Words",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 30,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 40,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "wordsperminute",
|
||||||
|
"title": "Words per minute",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 31,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 38,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "integer",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "resolution",
|
||||||
|
"title": "Resolution",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 32,
|
||||||
|
"array": true,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 20,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "pixels",
|
||||||
|
"title": "Pixels",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": true,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 33,
|
||||||
|
"array": false,
|
||||||
|
"type": "integer",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 21,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": true,
|
||||||
|
"group": false,
|
||||||
|
"name": "size",
|
||||||
|
"title": "Size",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": true,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 34,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 19,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "bitrate",
|
||||||
|
"title": "Bitrate",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 35,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 2,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": true,
|
||||||
|
"group": false,
|
||||||
|
"name": "files",
|
||||||
|
"title": "Files",
|
||||||
|
"default": false,
|
||||||
|
"align": "right",
|
||||||
|
"totals": true,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 36,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 18,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "string",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "filename",
|
||||||
|
"title": "Filename",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 37,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 36,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "date",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "published",
|
||||||
|
"title": "Published",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 38,
|
||||||
|
"array": false,
|
||||||
|
"type": "date",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 9,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "date",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "modified",
|
||||||
|
"title": "Modified",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 90,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 39,
|
||||||
|
"array": false,
|
||||||
|
"type": "date",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 5,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": "date",
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "popularity",
|
||||||
|
"title": "Popularity",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 60,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "-",
|
||||||
|
"position": 40,
|
||||||
|
"array": false,
|
||||||
|
"type": "date",
|
||||||
|
"find": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 35,
|
||||||
|
"model": "item.property",
|
||||||
|
"fields": {
|
||||||
|
"sort": null,
|
||||||
|
"admin": false,
|
||||||
|
"group": false,
|
||||||
|
"name": "dialog",
|
||||||
|
"title": "Dialog",
|
||||||
|
"default": false,
|
||||||
|
"align": "left",
|
||||||
|
"totals": false,
|
||||||
|
"autocomplete": false,
|
||||||
|
"width": 180,
|
||||||
|
"removable": true,
|
||||||
|
"operator": "",
|
||||||
|
"position": 100,
|
||||||
|
"array": false,
|
||||||
|
"type": "title",
|
||||||
|
"find": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -5,22 +5,12 @@ from django.contrib import admin
|
||||||
|
|
||||||
import models
|
import models
|
||||||
|
|
||||||
|
class ItemAdmin(admin.ModelAdmin):
|
||||||
class BinAdmin(admin.ModelAdmin):
|
search_fields = ['itemId', 'data', 'external_data']
|
||||||
search_fields = ['name', 'title']
|
admin.site.register(models.Item, ItemAdmin)
|
||||||
admin.site.register(models.Bin, BinAdmin)
|
|
||||||
|
|
||||||
class PropertyAdmin(admin.ModelAdmin):
|
class PropertyAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['name', 'title']
|
search_fields = ['name', 'title']
|
||||||
admin.site.register(models.Property, PropertyAdmin)
|
admin.site.register(models.Property, PropertyAdmin)
|
||||||
|
|
||||||
|
|
||||||
class PlaceAdmin(admin.ModelAdmin):
|
|
||||||
search_fields = ['name']
|
|
||||||
admin.site.register(models.Place, PlaceAdmin)
|
|
||||||
|
|
||||||
|
|
||||||
class EventAdmin(admin.ModelAdmin):
|
|
||||||
search_fields = ['name']
|
|
||||||
admin.site.register(models.Event, EventAdmin)
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from urllib2 import unquote
|
from urllib2 import unquote
|
||||||
import json
|
from ox.utils import json
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
|
@ -29,99 +29,23 @@ import load
|
||||||
import utils
|
import utils
|
||||||
from archive import extract
|
from archive import extract
|
||||||
|
|
||||||
|
from layer.models import Layer
|
||||||
|
from person.models import getPersonSort, Person
|
||||||
|
|
||||||
class Bin(models.Model):
|
|
||||||
class Meta:
|
|
||||||
ordering = ('position', )
|
|
||||||
|
|
||||||
name = models.CharField(null=True, max_length=255, unique=True)
|
|
||||||
title = models.CharField(null=True, max_length=255)
|
|
||||||
#text, string, string from list(fixme), event, place, person
|
|
||||||
type = models.CharField(null=True, max_length=255)
|
|
||||||
position = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
overlapping = models.BooleanField(default=True)
|
|
||||||
enabled = models.BooleanField(default=True)
|
|
||||||
|
|
||||||
enabled = models.BooleanField(default=True)
|
|
||||||
public = models.BooleanField(default=True) #false=users only see there own bins
|
|
||||||
subtitle = models.BooleanField(default=True) #bis can be displayed as subtitle, only one bin
|
|
||||||
|
|
||||||
find = models.BooleanField(default=True)
|
|
||||||
#words / item duration(wpm), total words, cuts per minute, cuts, number of layers, number of layers/duration
|
|
||||||
sort = models.CharField(null=True, max_length=255)
|
|
||||||
|
|
||||||
def properties(self):
|
|
||||||
p = {}
|
|
||||||
if self.find:
|
|
||||||
p[self.name] = {'type': 'bin', 'find': True}
|
|
||||||
if self.sort:
|
|
||||||
print 'FIXME: need to add sort stuff'
|
|
||||||
return p
|
|
||||||
|
|
||||||
properties = {
|
|
||||||
'title': {'type': 'string', 'sort': 'title', 'find': True},
|
|
||||||
'director': {'type': 'person', 'array': True, 'sort': 'string', 'find': True, 'group': True},
|
|
||||||
'country': {'type': 'string', 'array': True, 'sort': 'sring', 'find': True, 'group': True},
|
|
||||||
'year': {'type': 'string', 'sort': 'string', 'find': True, 'group': True},
|
|
||||||
'language': {'type': 'string', 'array': True, 'sort': 'string', 'find': True, 'group': True},
|
|
||||||
'runtime': {'type': 'integer', 'sort': 'integer'},
|
|
||||||
'writer': {'type': 'person', 'array': True, 'sort': 'string', 'find': True},
|
|
||||||
'producer': {'type': 'person', 'array': True, 'sort': 'string', 'find': True},
|
|
||||||
'cinematographer': {'type': 'person', 'array': True, 'sort': 'string', 'find': True},
|
|
||||||
'editor': {'type': 'person', 'array': True, 'sort': 'string', 'find': True},
|
|
||||||
'actors': {'type': 'role', 'array': True, 'sort': 'length', 'find': True},
|
|
||||||
'genre': {'type': 'string', 'array': True, 'sort': 'length', 'find': True, 'group': True},
|
|
||||||
'keywords': {'type': 'string', 'array': True, 'sort': 'length', 'find': True},
|
|
||||||
'summary': {'type': 'title', 'sort': 'length', 'find': True},
|
|
||||||
'trivia': {'type': 'title', 'sort': 'length', 'find': True},
|
|
||||||
'releasedate': {'type': 'date', 'sort': 'date', 'find': True},
|
|
||||||
'runtime': {'type': 'integer', 'sort': 'integer', 'totals': True},
|
|
||||||
|
|
||||||
'budget': {'type': 'float', 'sort': 'float'},
|
|
||||||
'gross': {'type': 'float', 'sort': 'float'},
|
|
||||||
'profit': {'type': 'float', 'sort': 'float'},
|
|
||||||
|
|
||||||
'rating': {'type': 'integer', 'sort': 'integer'},
|
|
||||||
'votes': {'type': 'integer', 'sort': 'integer'},
|
|
||||||
'published': {'type': 'date', 'sort': 'date'},
|
|
||||||
'modified': {'type': 'date', 'sort': 'date'},
|
|
||||||
'popularity': {'type': 'date', 'sort': 'date'},
|
|
||||||
|
|
||||||
#file properties // are those even configurable? think not
|
|
||||||
'aspectratio': {'type': 'faction', 'sort': 'float'},
|
|
||||||
'duration': {'type': 'float', 'sort': 'float', 'totals': True, "admin": True},
|
|
||||||
'color': {'type': 'color', 'sort': 'color'},
|
|
||||||
'saturation': {'type': 'integer', 'sort': 'integer'},
|
|
||||||
'brightness': {'type': 'integer', 'sort': 'integer'},
|
|
||||||
'volume': {'type': 'integer', 'sort': 'integer'},
|
|
||||||
'resolution': {'type': 'integer', 'array': True, 'sort': 'integer'}, #FIXME
|
|
||||||
'pixels': {'type': 'integer', 'sort': 'string', 'totals': True},
|
|
||||||
'size': {'type': 'title', 'sort': 'string', 'totals': True, 'admin': True},
|
|
||||||
'bitrate': {'type': 'title', 'sort': 'string'},
|
|
||||||
'files': {'type': 'title', 'sort': 'string', 'totals': True, 'admin': True},
|
|
||||||
'filename': {'type': 'title', 'sort': 'string'},
|
|
||||||
|
|
||||||
#Layer properties // those need to be defined with bins
|
|
||||||
'dialog': {'type': 'title', 'find': True},
|
|
||||||
#'clips': {'type': 'title', 'sort': 'string'},
|
|
||||||
#'cuts': {'type': 'title', 'sort': 'string'},
|
|
||||||
'cutsperminute': {'type': 'integer', 'title': 'Cuts per minute', 'sort': 'string'},
|
|
||||||
'words': {'type': 'title', 'sort': 'string'},
|
|
||||||
'wordsperminute': {'type': 'integer','title': 'Words per minute', 'sort': 'string'},
|
|
||||||
}
|
|
||||||
|
|
||||||
def siteJson():
|
def siteJson():
|
||||||
r = {}
|
r = {}
|
||||||
r['findKeys'] = [{"id": "all", "title": "All"}]
|
r['findKeys'] = [{"id": "all", "title": "All"}]
|
||||||
for k in properties:
|
for p in Property.objects.all():
|
||||||
i = properties[k]
|
if p.find:
|
||||||
if i.get('find', False):
|
title = p.title
|
||||||
f = {"id": k, "title": i.get('title', k.capitalize())}
|
if not title:
|
||||||
if i.get('autocomplete', False):
|
title = p.name.capitalize()
|
||||||
f['autocomplete'] = True
|
f = {"id": p.name, "title": title}
|
||||||
|
f['autocomplete'] = p.autocomplete
|
||||||
r['findKeys'].append(f)
|
r['findKeys'].append(f)
|
||||||
r['groups'] = filter(lambda k: properties[k].get('group', False), properties.keys())
|
|
||||||
|
r['groups'] = [p.name for p in Property.objects.filter(group=True)]
|
||||||
r['itemViews'] = [
|
r['itemViews'] = [
|
||||||
{"id": "info", "title": "Info"},
|
{"id": "info", "title": "Info"},
|
||||||
{"id": "statistics", "title": "Statistics"},
|
{"id": "statistics", "title": "Statistics"},
|
||||||
|
@ -155,35 +79,34 @@ def siteJson():
|
||||||
{"id": "featured", "title": "Featured Lists"}
|
{"id": "featured", "title": "Featured Lists"}
|
||||||
]
|
]
|
||||||
r['sortKeys'] = []
|
r['sortKeys'] = []
|
||||||
for k in properties:
|
for p in Property.objects.exclude(sort=''):
|
||||||
i = properties[k]
|
title = p.title
|
||||||
if 'sort' in i:
|
if not title:
|
||||||
f = {
|
title = p.name.capitalize()
|
||||||
"id": k,
|
|
||||||
"title": i.get('title', k.capitalize()),
|
f = {
|
||||||
"operator": i.get('operator', ''),
|
"id": p.name,
|
||||||
"align": i.get('align', 'left'),
|
"title": title,
|
||||||
"width": i.get('width', 180),
|
"operator": p.operator,
|
||||||
}
|
"align": p.align,
|
||||||
if not i.get('removable', True):
|
"width": p.width,
|
||||||
f['removable'] = False
|
}
|
||||||
r['sortKeys'].append(f)
|
if not p.removable:
|
||||||
|
f['removable'] = False
|
||||||
|
r['sortKeys'].append(f)
|
||||||
|
r['sortKeys'].append([{"id": "id", "title": "ID", "operator": "", "align": "left", "width": 90}])
|
||||||
|
|
||||||
r['totals'] = [{"id": "items"}]
|
r['totals'] = [{"id": "items"}]
|
||||||
for k in properties:
|
for p in Property.objects.filter(totals=True):
|
||||||
i = properties[k]
|
f = {'id': p.name, 'admin': p.admin}
|
||||||
if i.get('totals', False):
|
r['totals'].append(f)
|
||||||
f = {"id": k}
|
|
||||||
if i.get('admin', False):
|
|
||||||
f['admin'] = True
|
|
||||||
r['totals'].append(f)
|
|
||||||
|
|
||||||
#FIXME: defaults should also be populated from properties
|
#FIXME: defaults should also be populated from properties
|
||||||
r["user"] = {
|
r["user"] = {
|
||||||
"group": "guest",
|
"group": "guest",
|
||||||
"preferences": {},
|
"preferences": {},
|
||||||
"ui": {
|
"ui": {
|
||||||
"columns": ["id", "title", "director", "country", "year", "language", "genre"],
|
"columns": ["id"] + [p.name for p in Property.objects.filter(default=True)],
|
||||||
"findQuery": {"conditions": [], "operator": ""},
|
"findQuery": {"conditions": [], "operator": ""},
|
||||||
"groupsQuery": {"conditions": [], "operator": "|"},
|
"groupsQuery": {"conditions": [], "operator": "|"},
|
||||||
"groupsSize": 128,
|
"groupsSize": 128,
|
||||||
|
@ -196,10 +119,8 @@ def siteJson():
|
||||||
"showInfo": True,
|
"showInfo": True,
|
||||||
"showLists": True,
|
"showLists": True,
|
||||||
"showMovies": True,
|
"showMovies": True,
|
||||||
"sort": [
|
"sort": settings.DEFAULT_SORT,
|
||||||
{"key": "director", "operator": ""}
|
"theme": settings.DEFAULT_THEME
|
||||||
],
|
|
||||||
"theme": "classic"
|
|
||||||
},
|
},
|
||||||
"username": ""
|
"username": ""
|
||||||
}
|
}
|
||||||
|
@ -252,16 +173,25 @@ def getItem(info):
|
||||||
class Property(models.Model):
|
class Property(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('position', )
|
ordering = ('position', )
|
||||||
|
verbose_name_plural = "Properties"
|
||||||
|
|
||||||
name = models.CharField(null=True, max_length=255, unique=True)
|
name = models.CharField(null=True, max_length=255, unique=True)
|
||||||
title = models.CharField(null=True, max_length=255)
|
title = models.CharField(null=True, max_length=255, blank=True)
|
||||||
#text, string, string from list(fixme), event, place, person
|
#text, string, string from list(fixme), event, place, person
|
||||||
type = models.CharField(null=True, max_length=255)
|
type = models.CharField(null=True, max_length=255)
|
||||||
array = models.BooleanField(default=False)
|
array = models.BooleanField(default=False)
|
||||||
position = models.IntegerField(default=0)
|
position = models.IntegerField(default=0)
|
||||||
|
width = models.IntegerField(default=180)
|
||||||
|
align = models.CharField(null=True, max_length=255, default='left')
|
||||||
|
operator = models.CharField(null=True, max_length=5, default='', blank=True)
|
||||||
|
default = models.BooleanField('Enabled by default', default=False)
|
||||||
|
removable = models.BooleanField(default=True)
|
||||||
|
|
||||||
#sort values: title, string, integer, float, date
|
#sort values: title, string, integer, float, date
|
||||||
sort = models.CharField(null=True, max_length=255)
|
sort = models.CharField(null=True, max_length=255, blank=True)
|
||||||
|
find = models.BooleanField(default=False)
|
||||||
|
autocomplete = models.BooleanField(default=False)
|
||||||
|
group = models.BooleanField(default=False)
|
||||||
|
|
||||||
totals = models.BooleanField(default=False)
|
totals = models.BooleanField(default=False)
|
||||||
admin = models.BooleanField(default=False)
|
admin = models.BooleanField(default=False)
|
||||||
|
@ -279,6 +209,11 @@ class Property(models.Model):
|
||||||
j[key] = value
|
j[key] = value
|
||||||
return j
|
return j
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.title:
|
||||||
|
self.title = self.name.capitalize()
|
||||||
|
super(Property, self).save(*args, **kwargs)
|
||||||
|
|
||||||
class Item(models.Model):
|
class Item(models.Model):
|
||||||
person_keys = ('director', 'writer', 'producer', 'editor', 'cinematographer', 'actor', 'character')
|
person_keys = ('director', 'writer', 'producer', 'editor', 'cinematographer', 'actor', 'character')
|
||||||
facet_keys = person_keys + ('country', 'language', 'genre', 'keyword')
|
facet_keys = person_keys + ('country', 'language', 'genre', 'keyword')
|
||||||
|
@ -905,191 +840,10 @@ class Facet(models.Model):
|
||||||
self.value_sort = self.value
|
self.value_sort = self.value
|
||||||
super(Facet, self).save(*args, **kwargs)
|
super(Facet, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def getPersonSort(name):
|
|
||||||
person, created = Person.objects.get_or_create(name=name)
|
|
||||||
name_sort = unicodedata.normalize('NFKD', person.name_sort)
|
|
||||||
return name_sort
|
|
||||||
|
|
||||||
class Person(models.Model):
|
|
||||||
name = models.CharField(max_length=200)
|
|
||||||
name_sort = models.CharField(max_length=200)
|
|
||||||
|
|
||||||
#FIXME: how to deal with aliases
|
|
||||||
aliases = fields.TupleField(default=[])
|
|
||||||
|
|
||||||
imdbId = models.CharField(max_length=7, blank=True)
|
|
||||||
wikipediaId = models.CharField(max_length=1000, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('name_sort', )
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if not self.name_sort:
|
|
||||||
self.name_sort = ox.normalize.canonicalName(self.name)
|
|
||||||
super(Person, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_or_create(model, name, imdbId=None):
|
|
||||||
if imdbId:
|
|
||||||
q = model.objects.filter(name=name, imdbId=imdbId)
|
|
||||||
else:
|
|
||||||
q = model.objects.all().filter(name=name)
|
|
||||||
if q.count() > 0:
|
|
||||||
o = q[0]
|
|
||||||
else:
|
|
||||||
o = model.objects.create(name=name)
|
|
||||||
if imdbId:
|
|
||||||
o.imdbId = imdbId
|
|
||||||
o.save()
|
|
||||||
return o
|
|
||||||
get_or_create = classmethod(get_or_create)
|
|
||||||
|
|
||||||
def json(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Place(models.Model):
|
|
||||||
'''
|
|
||||||
Places are named locations, they should have geographical information attached to them.
|
|
||||||
'''
|
|
||||||
|
|
||||||
name = models.CharField(max_length=200, unique=True)
|
|
||||||
name_sort = models.CharField(max_length=200)
|
|
||||||
manual = models.BooleanField(default=False)
|
|
||||||
items = models.ManyToManyField(Item, related_name='places')
|
|
||||||
wikipediaId = models.CharField(max_length=1000, blank=True)
|
|
||||||
|
|
||||||
#FIXME: how to deal with aliases
|
|
||||||
aliases = fields.TupleField(default=[])
|
|
||||||
|
|
||||||
#FIXME: geo data, is this good enough?
|
|
||||||
lat_sw = models.FloatField(default=0)
|
|
||||||
lng_sw = models.FloatField(default=0)
|
|
||||||
lat_ne = models.FloatField(default=0)
|
|
||||||
lng_ne = models.FloatField(default=0)
|
|
||||||
lat_center = models.FloatField(default=0)
|
|
||||||
lng_center = models.FloatField(default=0)
|
|
||||||
area = models.FloatField(default=-1)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('name_sort', )
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def json(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if not self.name_sort:
|
|
||||||
self.name_sort = self.name
|
|
||||||
|
|
||||||
#update center
|
|
||||||
self.lat_center = ox.location.center(self.lat_sw, self.lat_ne)
|
|
||||||
self.lng_center = ox.location.center(self.lng_sw, self.lng_ne)
|
|
||||||
|
|
||||||
#update area
|
|
||||||
self.area = location.area(self.lat_sw, self.lng_sw, self.lat_ne, self.lng_ne)
|
|
||||||
|
|
||||||
super(Place, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
class Event(models.Model):
|
|
||||||
'''
|
|
||||||
Events are events in time that can be once or recurring,
|
|
||||||
From Mondays to Spring to 1989 to Roman Empire
|
|
||||||
'''
|
|
||||||
name = models.CharField(null=True, max_length=255, unique=True)
|
|
||||||
name_sort = models.CharField(null=True, max_length=255, unique=True)
|
|
||||||
wikipediaId = models.CharField(max_length=1000, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('name_sort', )
|
|
||||||
|
|
||||||
#FIXME: how to deal with aliases
|
|
||||||
aliases = fields.TupleField(default=[])
|
|
||||||
|
|
||||||
#once|year|week|day
|
|
||||||
recurring = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
#start yyyy-mm-dd|mm-dd|dow 00:00|00:00
|
|
||||||
#end yyyy-mm-dd|mm-dd|dow 00:00|00:01
|
|
||||||
start = models.CharField(null=True, max_length=255)
|
|
||||||
end = models.CharField(null=True, max_length=255)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if not self.name_sort:
|
|
||||||
self.name_sort = self.name
|
|
||||||
super(Event, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
class ReviewWhitelist(models.Model):
|
class ReviewWhitelist(models.Model):
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
url = models.CharField(max_length=255, unique=True)
|
url = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
class List(models.Model):
|
|
||||||
class Meta:
|
|
||||||
unique_together = ("user", "name")
|
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
modified = models.DateTimeField(auto_now=True)
|
|
||||||
user = models.ForeignKey(User)
|
|
||||||
name = models.CharField(max_length=255)
|
|
||||||
public = models.BooleanField(default=False)
|
|
||||||
items = models.ManyToManyField(Item, related_name='lists', through='ListItem')
|
|
||||||
|
|
||||||
def add(self, item):
|
|
||||||
q = self.items.filter(id=item.id)
|
|
||||||
if q.count() == 0:
|
|
||||||
l = ListItem()
|
|
||||||
l.list = self
|
|
||||||
l.item = item
|
|
||||||
l.save()
|
|
||||||
|
|
||||||
def remove(self, item):
|
|
||||||
self.ListItem.objects.all().filter(item=item, list=self).delete()
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return u'%s (%s)' % (self.title, unicode(self.user))
|
|
||||||
|
|
||||||
def editable(self, user):
|
|
||||||
#FIXME: make permissions work
|
|
||||||
if self.user == user or user.has_perm('Ox.admin'):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
class ListItem(models.Model):
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
modified = models.DateTimeField(auto_now=True)
|
|
||||||
list = models.ForeignKey(List)
|
|
||||||
item = models.ForeignKey(Item)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return u'%s in %s' % (unicode(self.item), unicode(self.list))
|
|
||||||
|
|
||||||
class Layer(models.Model):
|
|
||||||
#FIXME: here having a item,start index would be good
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
modified = models.DateTimeField(auto_now=True)
|
|
||||||
user = models.ForeignKey(User)
|
|
||||||
item = models.ForeignKey(Item)
|
|
||||||
|
|
||||||
#seconds
|
|
||||||
start = models.FloatField(default=-1)
|
|
||||||
stop = models.FloatField(default=-1)
|
|
||||||
|
|
||||||
type = models.CharField(blank=True, max_length=255)
|
|
||||||
value = models.TextField()
|
|
||||||
|
|
||||||
#FIXME: relational layers, Locations, clips etc
|
|
||||||
#location = models.ForeignKey('Location', default=None)
|
|
||||||
|
|
||||||
def editable(self, user):
|
|
||||||
if user.is_authenticated():
|
|
||||||
if obj.user == user.id or user.has_perm('0x.admin'):
|
|
||||||
return True
|
|
||||||
if user.groups.filter(id__in=obj.groups.all()).count() > 0:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
class Collection(models.Model):
|
class Collection(models.Model):
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
|
@ -17,10 +17,7 @@ from django.shortcuts import render_to_response, get_object_or_404, get_list_or_
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
try:
|
from ox.utils import json
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
from django.utils import simplejson as 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
|
||||||
|
@ -263,156 +260,6 @@ def api_removeItem(request):
|
||||||
response = json_response(status=403, text='permission denied')
|
response = json_response(status=403, text='permission denied')
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
|
|
||||||
@login_required_json
|
|
||||||
def api_addLayer(request):
|
|
||||||
'''
|
|
||||||
param data
|
|
||||||
{key: value}
|
|
||||||
return {'status': {'code': int, 'text': string},
|
|
||||||
'data': {}}
|
|
||||||
'''
|
|
||||||
response = {'status': {'code': 501, 'text': 'not implemented'}}
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
@login_required_json
|
|
||||||
def api_removeLayer(request):
|
|
||||||
'''
|
|
||||||
param data
|
|
||||||
{key: value}
|
|
||||||
return {'status': {'code': int, 'text': string},
|
|
||||||
'data': {}}
|
|
||||||
'''
|
|
||||||
response = {'status': {'code': 501, 'text': 'not implemented'}}
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
@login_required_json
|
|
||||||
def api_editLayer(request):
|
|
||||||
'''
|
|
||||||
param data
|
|
||||||
{key: value}
|
|
||||||
return {'status': {'code': int, 'text': string},
|
|
||||||
'data': {}}
|
|
||||||
'''
|
|
||||||
response = json_response({})
|
|
||||||
data = json.loads(request.POST['data'])
|
|
||||||
layer = get_object_or_404_json(models.Layer, pk=data['id'])
|
|
||||||
if layer.editable(request.user):
|
|
||||||
response = json_response(status=501, text='not implemented')
|
|
||||||
else:
|
|
||||||
response = json_response(status=403, text='permission denied')
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
response = json_response(status=501, text='not implemented')
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
'''
|
|
||||||
List API
|
|
||||||
'''
|
|
||||||
@login_required_json
|
|
||||||
def api_addListItem(request):
|
|
||||||
'''
|
|
||||||
param data
|
|
||||||
{list: listId,
|
|
||||||
item: itemId,
|
|
||||||
quert: ...
|
|
||||||
}
|
|
||||||
return {'status': {'code': int, 'text': string},
|
|
||||||
'data': {}}
|
|
||||||
'''
|
|
||||||
data = json.loads(request.POST['data'])
|
|
||||||
list = get_object_or_404_json(models.List, pk=data['list'])
|
|
||||||
if 'item' in data:
|
|
||||||
item = get_object_or_404_json(models.Item, pk=data['item'])
|
|
||||||
if list.editable(request.user):
|
|
||||||
list.add(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:
|
|
||||||
response = json_response(status=501, text='not implemented')
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
@login_required_json
|
|
||||||
def api_removeListItem(request):
|
|
||||||
'''
|
|
||||||
param data
|
|
||||||
{list: listId,
|
|
||||||
item: itemId,
|
|
||||||
quert: ...
|
|
||||||
}
|
|
||||||
return {'status': {'code': int, 'text': string},
|
|
||||||
'data': {}}
|
|
||||||
'''
|
|
||||||
data = json.loads(request.POST['data'])
|
|
||||||
list = get_object_or_404_json(models.List, pk=data['list'])
|
|
||||||
if 'item' in data:
|
|
||||||
item = get_object_or_404_json(models.Item, pk=data['item'])
|
|
||||||
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:
|
|
||||||
response = json_response(status=501, text='not implemented')
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
@login_required_json
|
|
||||||
def api_addList(request):
|
|
||||||
'''
|
|
||||||
param data
|
|
||||||
{name: value}
|
|
||||||
return {'status': {'code': int, 'text': string},
|
|
||||||
'data': {}}
|
|
||||||
'''
|
|
||||||
data = json.loads(request.POST['data'])
|
|
||||||
if models.List.filter(name=data['name'], user=request.user).count() == 0:
|
|
||||||
list = models.List(name = data['name'], user=request.user)
|
|
||||||
list.save()
|
|
||||||
response = json_response(status=200, text='created')
|
|
||||||
else:
|
|
||||||
response = json_response(status=403, text='list name exists')
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
@login_required_json
|
|
||||||
def api_editList(request):
|
|
||||||
'''
|
|
||||||
param data
|
|
||||||
{key: value}
|
|
||||||
keys: name, public
|
|
||||||
return {'status': {'code': int, 'text': string},
|
|
||||||
'data': {}}
|
|
||||||
'''
|
|
||||||
data = json.loads(request.POST['data'])
|
|
||||||
list = get_object_or_404_json(models.List, pk=data['list'])
|
|
||||||
if list.editable(request.user):
|
|
||||||
for key in data:
|
|
||||||
if key in ('name', 'public'):
|
|
||||||
setattr(list, key, data['key'])
|
|
||||||
else:
|
|
||||||
response = json_response(status=403, text='not allowed')
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
def api_removeList(request):
|
|
||||||
'''
|
|
||||||
param data
|
|
||||||
{key: value}
|
|
||||||
return {'status': {'code': int, 'text': string},
|
|
||||||
'data': {}}
|
|
||||||
'''
|
|
||||||
data = json.loads(request.POST['data'])
|
|
||||||
list = get_object_or_404_json(models.List, pk=data['list'])
|
|
||||||
if list.editable(request.user):
|
|
||||||
list.delete()
|
|
||||||
else:
|
|
||||||
response = json_response(status=403, text='not allowed')
|
|
||||||
return render_to_json_response(response)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Poster API
|
Poster API
|
||||||
'''
|
'''
|
||||||
|
@ -485,7 +332,6 @@ def api_getImdbId(request):
|
||||||
response = json_response(status=404, text='not found')
|
response = json_response(status=404, text='not found')
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
media delivery
|
media delivery
|
||||||
'''
|
'''
|
||||||
|
|
0
pandora/layer/__init__.py
Normal file
0
pandora/layer/__init__.py
Normal file
16
pandora/layer/admin.py
Normal file
16
pandora/layer/admin.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
|
||||||
|
class BinAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['name', 'title']
|
||||||
|
admin.site.register(models.Bin, BinAdmin)
|
||||||
|
|
||||||
|
class LayerAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['name', 'title']
|
||||||
|
admin.site.register(models.Layer, LayerAdmin)
|
||||||
|
|
80
pandora/layer/models.py
Normal file
80
pandora/layer/models.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division, with_statement
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import os.path
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import unicodedata
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
from django.utils import simplejson as json
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.django import fields
|
||||||
|
import ox
|
||||||
|
from ox import stripTags
|
||||||
|
from ox.normalize import canonicalTitle, canonicalName
|
||||||
|
|
||||||
|
|
||||||
|
class Bin(models.Model):
|
||||||
|
class Meta:
|
||||||
|
ordering = ('position', )
|
||||||
|
|
||||||
|
name = models.CharField(null=True, max_length=255, unique=True)
|
||||||
|
title = models.CharField(null=True, max_length=255)
|
||||||
|
#text, string, string from list(fixme), event, place, person
|
||||||
|
type = models.CharField(null=True, max_length=255)
|
||||||
|
position = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
overlapping = models.BooleanField(default=True)
|
||||||
|
enabled = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
enabled = models.BooleanField(default=True)
|
||||||
|
public = models.BooleanField(default=True) #false=users only see there own bins
|
||||||
|
subtitle = models.BooleanField(default=True) #bis can be displayed as subtitle, only one bin
|
||||||
|
|
||||||
|
find = models.BooleanField(default=True)
|
||||||
|
#words / item duration(wpm), total words, cuts per minute, cuts, number of layers, number of layers/duration
|
||||||
|
sort = models.CharField(null=True, max_length=255)
|
||||||
|
|
||||||
|
def properties(self):
|
||||||
|
p = {}
|
||||||
|
if self.find:
|
||||||
|
p[self.name] = {'type': 'bin', 'find': True}
|
||||||
|
if self.sort:
|
||||||
|
print 'FIXME: need to add sort stuff'
|
||||||
|
return p
|
||||||
|
|
||||||
|
class Layer(models.Model):
|
||||||
|
#FIXME: here having a item,start index would be good
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
user = models.ForeignKey(User)
|
||||||
|
item = models.ForeignKey('item.Item')
|
||||||
|
|
||||||
|
#seconds
|
||||||
|
start = models.FloatField(default=-1)
|
||||||
|
stop = models.FloatField(default=-1)
|
||||||
|
|
||||||
|
type = models.CharField(blank=True, max_length=255)
|
||||||
|
value = models.TextField()
|
||||||
|
|
||||||
|
#FIXME: relational layers, Locations, clips etc
|
||||||
|
#location = models.ForeignKey('Location', default=None)
|
||||||
|
|
||||||
|
def editable(self, user):
|
||||||
|
if user.is_authenticated():
|
||||||
|
if obj.user == user.id or user.has_perm('0x.admin'):
|
||||||
|
return True
|
||||||
|
if user.groups.filter(id__in=obj.groups.all()).count() > 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
23
pandora/layer/tests.py
Normal file
23
pandora/layer/tests.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
This file demonstrates two different styles of tests (one doctest and one
|
||||||
|
unittest). These will both pass when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace these with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
class SimpleTest(TestCase):
|
||||||
|
def test_basic_addition(self):
|
||||||
|
"""
|
||||||
|
Tests that 1 + 1 always equals 2.
|
||||||
|
"""
|
||||||
|
self.failUnlessEqual(1 + 1, 2)
|
||||||
|
|
||||||
|
__test__ = {"doctest": """
|
||||||
|
Another way to test that 1 + 1 is equal to 2.
|
||||||
|
|
||||||
|
>>> 1 + 1 == 2
|
||||||
|
True
|
||||||
|
"""}
|
||||||
|
|
69
pandora/layer/views.py
Normal file
69
pandora/layer/views.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from urllib2 import unquote
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db.models import Q, Avg, Count, Sum
|
||||||
|
from django.http import HttpResponse, Http404
|
||||||
|
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404, redirect
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.utils import 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.http import HttpFileResponse
|
||||||
|
import ox
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def api_addLayer(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
{key: value}
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {}}
|
||||||
|
'''
|
||||||
|
response = {'status': {'code': 501, 'text': 'not implemented'}}
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def api_removeLayer(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
{key: value}
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {}}
|
||||||
|
'''
|
||||||
|
response = {'status': {'code': 501, 'text': 'not implemented'}}
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def api_editLayer(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
{key: value}
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {}}
|
||||||
|
'''
|
||||||
|
response = json_response({})
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
layer = get_object_or_404_json(models.Layer, pk=data['id'])
|
||||||
|
if layer.editable(request.user):
|
||||||
|
response = json_response(status=501, text='not implemented')
|
||||||
|
else:
|
||||||
|
response = json_response(status=403, text='permission denied')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
response = json_response(status=501, text='not implemented')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
0
pandora/person/__init__.py
Normal file
0
pandora/person/__init__.py
Normal file
70
pandora/person/models.py
Normal file
70
pandora/person/models.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division, with_statement
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import os.path
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import unicodedata
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
from django.utils import simplejson as json
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.django import fields
|
||||||
|
import ox
|
||||||
|
from ox import stripTags
|
||||||
|
from ox.normalize import canonicalTitle, canonicalName
|
||||||
|
|
||||||
|
|
||||||
|
def getPersonSort(name):
|
||||||
|
person, created = Person.objects.get_or_create(name=name)
|
||||||
|
name_sort = unicodedata.normalize('NFKD', person.name_sort)
|
||||||
|
return name_sort
|
||||||
|
|
||||||
|
class Person(models.Model):
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
name_sort = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
#FIXME: how to deal with aliases
|
||||||
|
aliases = fields.TupleField(default=[])
|
||||||
|
|
||||||
|
imdbId = models.CharField(max_length=7, blank=True)
|
||||||
|
wikipediaId = models.CharField(max_length=1000, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name_sort', )
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.name_sort:
|
||||||
|
self.name_sort = ox.normalize.canonicalName(self.name)
|
||||||
|
super(Person, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_or_create(model, name, imdbId=None):
|
||||||
|
if imdbId:
|
||||||
|
q = model.objects.filter(name=name, imdbId=imdbId)
|
||||||
|
else:
|
||||||
|
q = model.objects.all().filter(name=name)
|
||||||
|
if q.count() > 0:
|
||||||
|
o = q[0]
|
||||||
|
else:
|
||||||
|
o = model.objects.create(name=name)
|
||||||
|
if imdbId:
|
||||||
|
o.imdbId = imdbId
|
||||||
|
o.save()
|
||||||
|
return o
|
||||||
|
get_or_create = classmethod(get_or_create)
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return self.name
|
||||||
|
|
23
pandora/person/tests.py
Normal file
23
pandora/person/tests.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
This file demonstrates two different styles of tests (one doctest and one
|
||||||
|
unittest). These will both pass when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace these with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
class SimpleTest(TestCase):
|
||||||
|
def test_basic_addition(self):
|
||||||
|
"""
|
||||||
|
Tests that 1 + 1 always equals 2.
|
||||||
|
"""
|
||||||
|
self.failUnlessEqual(1 + 1, 2)
|
||||||
|
|
||||||
|
__test__ = {"doctest": """
|
||||||
|
Another way to test that 1 + 1 is equal to 2.
|
||||||
|
|
||||||
|
>>> 1 + 1 == 2
|
||||||
|
True
|
||||||
|
"""}
|
||||||
|
|
1
pandora/person/views.py
Normal file
1
pandora/person/views.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Create your views here.
|
0
pandora/place/__init__.py
Normal file
0
pandora/place/__init__.py
Normal file
13
pandora/place/admin.py
Normal file
13
pandora/place/admin.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
|
||||||
|
class PlaceAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['name']
|
||||||
|
admin.site.register(models.Place, PlaceAdmin)
|
||||||
|
|
||||||
|
|
40
pandora/place/managers.py
Normal file
40
pandora/place/managers.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ox.utils import json
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.models import Q, Manager
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
class PlaceManager(Manager):
|
||||||
|
def get_query_set(self):
|
||||||
|
return super(PlaceManager, self).get_query_set()
|
||||||
|
|
||||||
|
def find(self, q='', f="globe", sw_lat=-180.0, sw_lng=-180.0, ne_lat=180.0, ne_lng=180.0):
|
||||||
|
qs = self.get_query_set()
|
||||||
|
qs = qs.filter(Q(
|
||||||
|
Q(Q(sw_lat__gt=sw_lat)|Q(sw_lat__lt=ne_lat)|Q(sw_lng__gt=sw_lng)|Q(sw_lng__lt=ne_lng)) &
|
||||||
|
Q(Q(sw_lat__gt=sw_lat)|Q(sw_lat__lt=ne_lat)|Q(sw_lng__lt=ne_lng)|Q(ne_lng__gt=ne_lng)) &
|
||||||
|
Q(Q(ne_lat__gt=sw_lat)|Q(ne_lat__lt=ne_lat)|Q(sw_lng__gt=sw_lng)|Q(sw_lng__lt=ne_lng)) &
|
||||||
|
Q(Q(ne_lat__gt=sw_lat)|Q(ne_lat__lt=ne_lat)|Q(ne_lng__gt=sw_lng)|Q(ne_lng__lt=ne_lng))
|
||||||
|
))
|
||||||
|
if q:
|
||||||
|
qs = qs.filter(name_find__icontains, q)
|
||||||
|
return qs
|
||||||
|
'''
|
||||||
|
#only return locations that have layers of videos visible to current user
|
||||||
|
if not identity.current.anonymous:
|
||||||
|
user = identity.current.user
|
||||||
|
if not user.in_group('admin'):
|
||||||
|
query = AND(query,
|
||||||
|
id == Layer.q.locationID, Layer.q.videoID == Video.q.id,
|
||||||
|
OR(Video.q.public == True, Video.q.creatorID == user.id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
query = AND(query,
|
||||||
|
id == Layer.q.locationID, Layer.q.videoID == Video.q.id,
|
||||||
|
Video.q.public == True)
|
||||||
|
'''
|
66
pandora/place/models.py
Normal file
66
pandora/place/models.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division, with_statement
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.django import fields
|
||||||
|
|
||||||
|
import managers
|
||||||
|
|
||||||
|
class Place(models.Model):
|
||||||
|
'''
|
||||||
|
Places are named locations, they should have geographical information attached to them.
|
||||||
|
'''
|
||||||
|
|
||||||
|
name = models.CharField(max_length=200, unique=True)
|
||||||
|
name_sort = models.CharField(max_length=200)
|
||||||
|
name_find = models.TextField(default='', editable=False)
|
||||||
|
|
||||||
|
geoname = models.CharField(max_length=1024, unique=True)
|
||||||
|
geoname_reverse = models.CharField(max_length=1024, unique=True)
|
||||||
|
|
||||||
|
wikipediaId = models.CharField(max_length=1000, blank=True)
|
||||||
|
aliases = fields.TupleField(default=[])
|
||||||
|
|
||||||
|
sw_lat = models.FloatField(default=0)
|
||||||
|
sw_lng = models.FloatField(default=0)
|
||||||
|
ne_lat = models.FloatField(default=0)
|
||||||
|
ne_lng = models.FloatField(default=0)
|
||||||
|
center_lat = models.FloatField(default=0)
|
||||||
|
center_lng = models.FloatField(default=0)
|
||||||
|
area = models.FloatField(default=-1)
|
||||||
|
|
||||||
|
objects = managers.PlaceManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name_sort', )
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
j = {}
|
||||||
|
for key in ('name', 'name_sort', 'aliases', 'geoname', 'geoname_reversed',
|
||||||
|
'sw_lat', 'sw_lng', 'ne_lat', 'ne_lng',
|
||||||
|
'center_lat', 'center_lng'):
|
||||||
|
j[key] = getattr(self, key)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.name_sort:
|
||||||
|
self.name_sort = self.name
|
||||||
|
self.geoname_reverse = ', '.join(reversed(self.geoname.split(', ')))
|
||||||
|
|
||||||
|
self.name_find = '|%s|'%'|'.join([self.name] + self.aliases)
|
||||||
|
|
||||||
|
#update center
|
||||||
|
self.lat_center = ox.location.center(self.lat_sw, self.lat_ne)
|
||||||
|
self.lng_center = ox.location.center(self.lng_sw, self.lng_ne)
|
||||||
|
|
||||||
|
#update area
|
||||||
|
self.area = location.area(self.lat_sw, self.lng_sw, self.lat_ne, self.lng_ne)
|
||||||
|
|
||||||
|
super(Place, self).save(*args, **kwargs)
|
||||||
|
|
23
pandora/place/tests.py
Normal file
23
pandora/place/tests.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
This file demonstrates two different styles of tests (one doctest and one
|
||||||
|
unittest). These will both pass when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace these with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
class SimpleTest(TestCase):
|
||||||
|
def test_basic_addition(self):
|
||||||
|
"""
|
||||||
|
Tests that 1 + 1 always equals 2.
|
||||||
|
"""
|
||||||
|
self.failUnlessEqual(1 + 1, 2)
|
||||||
|
|
||||||
|
__test__ = {"doctest": """
|
||||||
|
Another way to test that 1 + 1 is equal to 2.
|
||||||
|
|
||||||
|
>>> 1 + 1 == 2
|
||||||
|
True
|
||||||
|
"""}
|
||||||
|
|
131
pandora/place/views.py
Normal file
131
pandora/place/views.py
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from urllib2 import unquote
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db.models import Q, Avg, Count, Sum
|
||||||
|
from django.http import HttpResponse, Http404
|
||||||
|
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404, redirect
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ox.utils import 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.http import HttpFileResponse
|
||||||
|
import ox
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
'''
|
||||||
|
fixme, require admin
|
||||||
|
'''
|
||||||
|
@login_required_json
|
||||||
|
def api_addPlace(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
{
|
||||||
|
'place': dict
|
||||||
|
}
|
||||||
|
place contains key/value pairs with place propterties
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
exists = False
|
||||||
|
names = [data['place']['name']] + data['place']['aliases']
|
||||||
|
for name in names:
|
||||||
|
if models.Place.objects.filter(name_find__icontains=u'|%s|'%data['name']).count() != 0:
|
||||||
|
exists = True
|
||||||
|
if not exists:
|
||||||
|
place = models.Place()
|
||||||
|
for key in data['place']:
|
||||||
|
setattr(place, key, data['place'][key])
|
||||||
|
place.save()
|
||||||
|
response = json_response(status=200, text='created')
|
||||||
|
else:
|
||||||
|
response = json_response(status=403, text='place name exists')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def api_editPlace(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
{
|
||||||
|
'id': placeid,
|
||||||
|
'place': dict
|
||||||
|
}
|
||||||
|
place contains key/value pairs with place propterties
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
place = get_object_or_404_json(models.Place, pk=data['id'])
|
||||||
|
if place.editable(request.user):
|
||||||
|
conflict = False
|
||||||
|
names = [data['place']['name']] + data['place']['aliases']
|
||||||
|
for name in names:
|
||||||
|
if models.Place.objects.filter(name_find__icontains=u'|%s|'%data['name']).exclude(id=place.id).count() != 0:
|
||||||
|
conflict = True
|
||||||
|
if not conflict:
|
||||||
|
for key in data['place']:
|
||||||
|
setattr(place, key, data['place'][key])
|
||||||
|
place.save()
|
||||||
|
response = json_response(status=200, text='updated')
|
||||||
|
else:
|
||||||
|
response = json_response(status=403, text='place name/alias conflict')
|
||||||
|
else:
|
||||||
|
response = json_response(status=403, text='permission denied')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def api_removePlace(request):
|
||||||
|
response = json_response(status=501, text='not implemented')
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
def api_findPlace(request):
|
||||||
|
'''
|
||||||
|
param data
|
||||||
|
{'query': query, 'sort': array, 'range': array, 'area': array}
|
||||||
|
|
||||||
|
query: query object, more on query syntax at
|
||||||
|
https://wiki.0x2620.org/wiki/pandora/QuerySyntax
|
||||||
|
sort: array of key, operator dics
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "year",
|
||||||
|
operator: "-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "director",
|
||||||
|
operator: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
range: result range, array [from, to]
|
||||||
|
area: [sw_lat, sw_lng, ne_lat, ne_lng] only return places in that square
|
||||||
|
|
||||||
|
with keys, items is list of dicts with requested properties:
|
||||||
|
return {'status': {'code': int, 'text': string},
|
||||||
|
'data': {items: array}}
|
||||||
|
|
||||||
|
Positions
|
||||||
|
param data
|
||||||
|
{'query': query, 'ids': []}
|
||||||
|
|
||||||
|
query: query object, more on query syntax at
|
||||||
|
https://wiki.0x2620.org/wiki/pandora/QuerySyntax
|
||||||
|
ids: ids of places for which positions are required
|
||||||
|
'''
|
||||||
|
data = json.loads(request.POST['data'])
|
||||||
|
response = json_response(status=200, text='ok')
|
||||||
|
response['data']['places'] = []
|
||||||
|
#FIXME: add coordinates to limit search
|
||||||
|
for p in Places.objects.find(data['query']):
|
||||||
|
response['data']['places'].append(p.json())
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
|
@ -114,16 +114,21 @@ INSTALLED_APPS = (
|
||||||
|
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'devserver',
|
'devserver',
|
||||||
'south',
|
# 'south',
|
||||||
'djcelery',
|
'djcelery',
|
||||||
|
|
||||||
'app',
|
'app',
|
||||||
'api',
|
'api',
|
||||||
'item',
|
|
||||||
'archive',
|
'archive',
|
||||||
'user',
|
'date',
|
||||||
|
'item',
|
||||||
|
'itemlist',
|
||||||
|
'layer',
|
||||||
|
'person',
|
||||||
|
'place',
|
||||||
'text',
|
'text',
|
||||||
'torrent',
|
'torrent',
|
||||||
|
'user',
|
||||||
)
|
)
|
||||||
|
|
||||||
AUTH_PROFILE_MODULE = 'user.UserProfile'
|
AUTH_PROFILE_MODULE = 'user.UserProfile'
|
||||||
|
@ -131,6 +136,9 @@ AUTH_PROFILE_MODULE = 'user.UserProfile'
|
||||||
#Video encoding settings
|
#Video encoding settings
|
||||||
#available profiles: 96p, 270p, 360p, 480p, 720p, 1080p
|
#available profiles: 96p, 270p, 360p, 480p, 720p, 1080p
|
||||||
|
|
||||||
|
DEFAULT_SORT = [{"key": "director", "operator": ""}]
|
||||||
|
DEFAULT_THEME = "classic"
|
||||||
|
|
||||||
VIDEO_PROFILE = '96p'
|
VIDEO_PROFILE = '96p'
|
||||||
VIDEO_DERIVATIVES = []
|
VIDEO_DERIVATIVES = []
|
||||||
VIDEO_H264 = False
|
VIDEO_H264 = False
|
||||||
|
|
|
@ -3,9 +3,22 @@
|
||||||
<head>
|
<head>
|
||||||
<title>{{sitename}} API</title>
|
<title>{{sitename}} API</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
<script type="text/javascript" src="/static/js/jquery/jquery.js"></script>
|
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/static/png/icon.16.png"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/oxjs/build/css/ox.ui.css"/>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/static/oxjs/build/js/jquery-1.4.2.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/jquery/jquery.videosupport.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/oxjs/build/js/ox.load.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/oxjs/build/js/ox.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/oxjs/build/js/ox.ui.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/pandora.api.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
<body></body>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<!--
|
||||||
|
<div id="static_apidocs">
|
||||||
<h1>{{sitename}} Public API</h1>
|
<h1>{{sitename}} Public API</h1>
|
||||||
<a href="http://code.0xdb.org/pandora/annotate/head%3A/pandora/backend/views.py">look inside</a>
|
<a href="http://code.0xdb.org/pandora/annotate/head%3A/pandora/backend/views.py">look inside</a>
|
||||||
<div>
|
<div>
|
||||||
|
@ -18,5 +31,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
-e svn+http://code.djangoproject.com/svn/django/branches/releases/1.2.X#egg=django
|
-e svn+http://code.djangoproject.com/svn/django/branches/releases/1.2.X#egg=django
|
||||||
South
|
South
|
||||||
chardet
|
|
||||||
-e bzr+http://code.0x2620.org/python-ox/#egg=python-ox
|
-e bzr+http://code.0x2620.org/python-ox/#egg=python-ox
|
||||||
-e bzr+http://code.0x2620.org/oxtimeline/#egg=oxtimeline
|
-e bzr+http://code.0x2620.org/oxtimeline/#egg=oxtimeline
|
||||||
simplejson
|
simplejson
|
||||||
#-e hg+https://django-ajax-filtered-fields.googlecode.com/hg/#egg=django-ajax-filtered-fields
|
#-e hg+https://django-ajax-filtered-fields.googlecode.com/hg/#egg=django-ajax-filtered-fields
|
||||||
#rabbitmq interface
|
#rabbitmq interface
|
||||||
-e git+git://github.com/ask/celery.git#egg=celery
|
celery
|
||||||
django-celery
|
django-celery
|
||||||
django_extensions
|
django_extensions
|
||||||
-e bzr+http://firefogg.org/dev/python-firefogg/#egg=python-firefogg
|
-e bzr+http://firefogg.org/dev/python-firefogg/#egg=python-firefogg
|
||||||
|
|
173
static/js/pandora.api.js
Executable file
173
static/js/pandora.api.js
Executable file
|
@ -0,0 +1,173 @@
|
||||||
|
/***
|
||||||
|
Pandora API
|
||||||
|
***/
|
||||||
|
|
||||||
|
var app = new Ox.App({
|
||||||
|
apiURL: '/api/',
|
||||||
|
config: '/site.json',
|
||||||
|
init: 'hello',
|
||||||
|
}).launch(function(data) {
|
||||||
|
Ox.print('data', data)
|
||||||
|
app.config = data.config;
|
||||||
|
app.user = data.user;
|
||||||
|
if (app.user.group == 'guest') {
|
||||||
|
app.user = data.config.user;
|
||||||
|
$.browser.safari && Ox.theme('modern');
|
||||||
|
}
|
||||||
|
|
||||||
|
app.$body = $('body');
|
||||||
|
app.$document = $(document);
|
||||||
|
app.$window = $(window);
|
||||||
|
//app.$body.html('');
|
||||||
|
|
||||||
|
app.$ui = {};
|
||||||
|
app.$ui.actionList = constructList();
|
||||||
|
app.$ui.actionInfo = Ox.Container().css({padding: '8px'});
|
||||||
|
|
||||||
|
app.api.apidoc(function(results) { app.docs = results.data.actions; });
|
||||||
|
|
||||||
|
var $main = new Ox.SplitPanel({
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
element: app.$ui.actionList,
|
||||||
|
size: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: app.$ui.actionInfo,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
orientation: 'horizontal'
|
||||||
|
});
|
||||||
|
|
||||||
|
$main.appendTo(app.$body);
|
||||||
|
});
|
||||||
|
|
||||||
|
function constructList() {
|
||||||
|
return new Ox.TextList({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
align: "left",
|
||||||
|
id: "name",
|
||||||
|
operator: "+",
|
||||||
|
title: "Name",
|
||||||
|
unique: true,
|
||||||
|
visible: true,
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columnsMovable: false,
|
||||||
|
columnsRemovable: false,
|
||||||
|
id: 'actionList',
|
||||||
|
request: function(data, callback) {
|
||||||
|
if(!data.keys) {
|
||||||
|
app.api.apidoc(function(results) {
|
||||||
|
var items = [];
|
||||||
|
$.each(results.data.actions, function(k) {items.push({'name': k})});
|
||||||
|
var result = {'data': {'items': items.length}};
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
app.api.apidoc(function(results) {
|
||||||
|
var items = [];
|
||||||
|
$.each(results.data.actions, function(k) {items.push({'name': k})});
|
||||||
|
var result = {'data': {'items': items}};
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
operator: "+"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).bindEvent({
|
||||||
|
select: function(event, data) {
|
||||||
|
var info = $('<div>');
|
||||||
|
$.each(data.ids, function(v, k) {
|
||||||
|
console.log(k)
|
||||||
|
info.append($("<h2>").html(k));
|
||||||
|
info.append($('<pre>').html(app.docs[k].replace('/\n/<br>\n/g')));
|
||||||
|
});
|
||||||
|
app.$ui.actionInfo.html(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
.bindEvent({
|
||||||
|
load: function(event, data) {
|
||||||
|
app.$ui.total.html(app.constructStatus('total', data));
|
||||||
|
data = [];
|
||||||
|
$.each(app.config.totals, function(i, v) {
|
||||||
|
data[v.id] = 0;
|
||||||
|
});
|
||||||
|
app.$ui.selected.html(app.constructStatus('selected', data));
|
||||||
|
},
|
||||||
|
open: function(event, data) {
|
||||||
|
//alert(data.toSource());
|
||||||
|
var $iframe = $('<iframe frameborder="0">')
|
||||||
|
.css({
|
||||||
|
width:'100%',
|
||||||
|
height: '99%',
|
||||||
|
border: 0,
|
||||||
|
margin: 0,
|
||||||
|
padding: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
var $dialog = new Ox.Dialog({
|
||||||
|
title: 'Downloading',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
title: 'Close',
|
||||||
|
click: function() {
|
||||||
|
$dialog.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
width: 800,
|
||||||
|
height: 400
|
||||||
|
})
|
||||||
|
.append($iframe)
|
||||||
|
.open();
|
||||||
|
|
||||||
|
app.api.find({
|
||||||
|
query: {
|
||||||
|
conditions: [{
|
||||||
|
key: 'id',
|
||||||
|
value: data.ids[0],
|
||||||
|
operator: '='
|
||||||
|
}],
|
||||||
|
operator: ''
|
||||||
|
},
|
||||||
|
keys: ['links'],
|
||||||
|
range: [0, 100]
|
||||||
|
}, function(result) {
|
||||||
|
var url = result.data.items[0].links[0];
|
||||||
|
$iframe.attr('src', 'http://anonym.to/?' + url);
|
||||||
|
|
||||||
|
//var url = result.data.items[0].links[0];
|
||||||
|
//document.location.href = url;
|
||||||
|
//window.open(url, "download");
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
select: function(event, data) {
|
||||||
|
app.api.find({
|
||||||
|
query: {
|
||||||
|
conditions: $.map(data.ids, function(id, i) {
|
||||||
|
return {
|
||||||
|
key: 'id',
|
||||||
|
value: id,
|
||||||
|
operator: '='
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
operator: '|'
|
||||||
|
}
|
||||||
|
}, function(result) {
|
||||||
|
app.$ui.selected.html(app.constructStatus('selected', result.data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
Loading…
Reference in a new issue