split models into apps, new api interface

This commit is contained in:
j 2010-11-26 16:07:24 +01:00
parent f833109c02
commit b8e5764f3d
32 changed files with 2033 additions and 488 deletions

7
README
View file

@ -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/

View file

@ -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
View file

12
pandora/date/admin.py Normal file
View 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
View 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
View 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
View 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
View 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)

View 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
}
}
]

View file

@ -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)

View file

@ -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

View file

@ -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,27 +79,26 @@ 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:
title = p.name.capitalize()
f = { f = {
"id": k, "id": p.name,
"title": i.get('title', k.capitalize()), "title": title,
"operator": i.get('operator', ''), "operator": p.operator,
"align": i.get('align', 'left'), "align": p.align,
"width": i.get('width', 180), "width": p.width,
} }
if not i.get('removable', True): if not p.removable:
f['removable'] = False f['removable'] = False
r['sortKeys'].append(f) 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):
f = {"id": k}
if i.get('admin', False):
f['admin'] = True
r['totals'].append(f) r['totals'].append(f)
#FIXME: defaults should also be populated from properties #FIXME: defaults should also be populated from properties
@ -183,7 +106,7 @@ def siteJson():
"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)

View file

@ -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
''' '''

View file

16
pandora/layer/admin.py Normal file
View 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
View 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
View 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
View 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)

View file

70
pandora/person/models.py Normal file
View 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
View 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
View file

@ -0,0 +1 @@
# Create your views here.

View file

13
pandora/place/admin.py Normal file
View 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
View 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
View 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
View 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
View 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)

View file

@ -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

View file

@ -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>

View file

@ -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
View 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));
});
}
});
}
*/