software powering 0xdb.org and Pad.ma is called pandora now

This commit is contained in:
j 2010-02-16 15:52:34 +05:30
commit 7c0e365a0a
46 changed files with 30 additions and 21 deletions

0
pandora/__init__.py Normal file
View file

0
pandora/app/__init__.py Normal file
View file

12
pandora/app/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 PageAdmin(admin.ModelAdmin):
search_fields = ['name', 'body']
admin.site.register(models.Page, PageAdmin)

13
pandora/app/models.py Normal file
View file

@ -0,0 +1,13 @@
from django.db import models
# Create your models here.
class Page(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
name = models.CharField(max_length=1024, unique=True)
body = models.TextField(blank=True)
def __unicode__(self):
return self.name

23
pandora/app/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
"""}

28
pandora/app/views.py Normal file
View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404
from django.template import RequestContext
from django.conf import settings
from oxdjango.shortcuts import json_response, render_to_json_response, get_object_or_404_json
import models
def index(request):
context = RequestContext(request, {'settings':settings})
return render_to_response('index.html', context)
def api_getPage(request):
data = json.loads(request.POST['data'])
name = data['page']
page = get_object_or_404_json(models.Archive, name=name)
response = json_response({'name': page.name, 'body': page.body})
return render_to_json_response(response)
return render_to_response('site.js', context, mimetype="application/javascript")
def site_js(request):
pages = models.Page.objects.all()
context = RequestContext(request, {'settings':settings, 'pages': pages})
return render_to_response('site.js', context, mimetype="application/javascript")

View file

43
pandora/backend/admin.py Normal file
View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.contrib import admin
from forms import FileAdminForm, MovieAdminForm, ArchiveFileAdminForm
import models
#class MovieImdbAdmin(admin.ModelAdmin):
# search_fields = ['imdbId', 'title']
#admin.site.register(models.MovieImdb, MovieImdbAdmin)
class MovieImdbInline(admin.StackedInline):
model = models.MovieImdb
class MovieOxdbInline(admin.StackedInline):
model = models.MovieOxdb
class MovieAdmin(admin.ModelAdmin):
search_fields = ['movieId', 'imdb__title', 'oxdb__title']
form = MovieAdminForm
#inlines = [MovieImdbInline, MovieOxdbInline]
admin.site.register(models.Movie, MovieAdmin)
class FileAdmin(admin.ModelAdmin):
search_fields = ['path', 'video_codec']
form = FileAdminForm
admin.site.register(models.File, FileAdmin)
class ArchiveFileAdmin(admin.ModelAdmin):
search_fields = ['path', 'archive__name']
form = ArchiveFileAdminForm
admin.site.register(models.ArchiveFile, ArchiveFileAdmin)
class ArchiveAdmin(admin.ModelAdmin):
search_fields = ['name']
admin.site.register(models.Archive, ArchiveAdmin)

33
pandora/backend/daemon.py Normal file
View file

@ -0,0 +1,33 @@
from carrot.connection import DjangoBrokerConnection
from carrot.messaging import Consumer, Publisher
from django.conf import settings
import load
import models
def send_bg_message(msg):
conn = DjangoBrokerConnection()
publisher = Publisher(connection=conn, exchange="oxdb-bg",
routing_key="oxdb-bg")
publisher.send(msg)
publisher.close()
def run():
conn = DjangoBrokerConnection()
consumer = Consumer(connection=conn, queue="oxdb-bg",
exchange="oxdb-bg",
routing_key="oxdb-bg")
def handle_background_tasks_callback(data, message):
print("Got bg message")
print data
if 'loadIMDb' in data:
imdbId = data['loadIMDb']
load.loadIMDb(imdbId)
elif 'findMovie' in data:
f = models.File.objects.get(pk=data['findMovie'])
f.findMovie()
message.ack()
consumer.register_callback(handle_background_tasks_callback)
consumer.wait() # Go into the consumer loop.

View file

@ -0,0 +1,44 @@
from carrot.connection import DjangoBrokerConnection
from carrot.messaging import Consumer, Publisher
from django.conf import settings
import load
import models
def send_encoder_message(msg):
conn = DjangoBrokerConnection()
publisher = Publisher(connection=conn, exchange="oxdb-encoder",
routing_key="oxdb-encoder")
publisher.send(msg)
publisher.close()
def run():
conn = DjangoBrokerConnection()
consumer = Consumer(connection=conn, queue="oxdb-encoder",
exchange="oxdb-encoder",
routing_key="oxdb-encoder")
def handle_background_tasks_callback(data, message):
print("Got encoder message")
print data
if 'extract' in data:
'''
update file stuff
create derivates and other related stuff for a file
'''
fileId = data['fileId']
f = models.File.objects.get(pk=fileId)
f.extract()
elif 'updateMovie' in data:
'''
update movie
create proxy stream and other related files extracted from movieFiles
'''
movieId = data['movieId']
m = models.Movie.objects.get(pk=fileId)
m.extract()
message.ack()
consumer.register_callback(handle_background_tasks_callback)
consumer.wait() # Go into the consumer loop.

View file

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
# GPL 2010
from __future__ import division
import re
import os
from os.path import abspath, join, dirname, exists
import shutil
import time
import warnings
import subprocess
import oxlib
import Image
import simplejson as json
img_extension='jpg'
def run_command(cmd, timeout=10):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while timeout > 0:
time.sleep(0.2)
timeout -= 0.2
if p.poll() != None:
return p.returncode
if p.poll() == None:
os.kill(p.pid, 9)
killedpid, stat = os.waitpid(p.pid, os.WNOHANG)
return p.returncode
def frame(videoFile, position, baseFolder, width=128, redo=False):
'''
params:
videoFile
position as float in seconds
baseFolder to write frames to
width of frame
redo boolean to extract file even if it exists
'''
#not using input file, to slow to extract frame right now
base_size = 320
frame = os.path.join(baseFolder, "%f.%s.%s" % (position, base_size, img_extension))
if exists(videoFile):
if redo or not exists(frame):
if not exists(baseFolder):
os.makedirs(baseFolder)
cmd = ['oggThumb', '-t', str(position), '-n', frame, '-s', '%dx0'%base_size, videoFile]
run_command(cmd)
if width != base_size:
frame_base = frame
frame = os.path.join(baseFolder, "%f.%s.%s" % (position, width, img_extension))
if not exists(frame):
resize_image(frame_base, frame, width)
return frame
def resize_image(image_source, image_output, width):
if exists(image_source):
source = Image.open(image_source)
source_width = source.size[0]
source_height = source.size[1]
height = int(width / (float(source_width) / source_height))
height = height - height % 2
if width < source_width:
resize_method = Image.ANTIALIAS
else:
resize_method = Image.BICUBIC
output = source.resize((width, height), resize_method)
output.save(image_output)

42
pandora/backend/forms.py Normal file
View file

@ -0,0 +1,42 @@
from ajax_filtered_fields.forms import AjaxManyToManyField, ForeignKeyByLetter
from django.conf import settings
from django import forms
import models
ajax_filtered_js = (
settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js",
settings.STATIC_URL + 'js/jquery/jquery.js',
settings.STATIC_URL + 'js/ajax_filtered_fields.js',
)
class FileAdminForm(forms.ModelForm):
movie = ForeignKeyByLetter(models.Movie, field_name='imdb__title')
class Meta:
model = models.File
class Media:
js = ajax_filtered_js
class ArchiveFileAdminForm(forms.ModelForm):
file = ForeignKeyByLetter(models.File, field_name='path')
class Meta:
model = models.ArchiveFile
class Media:
js = ajax_filtered_js
class MovieAdminForm(forms.ModelForm):
imdb = ForeignKeyByLetter(models.MovieImdb, field_name='title')
oxdb = ForeignKeyByLetter(models.MovieOxdb, field_name='title')
class Meta:
model = models.Movie
class Media:
js = ajax_filtered_js

175
pandora/backend/load.py Normal file
View file

@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import random
import os.path
from django.db import models
from django.contrib.auth.models import User
from oxlib import stripTags, findRe
import oxweb.imdb
import models
def debug(*msgs):
for m in msgs:
print m,
print
'''Import data from imdb into database,
param: impdb id
return: Movie Object, None if failed
'''
def loadIMDb(imdbId):
if len(imdbId) != 7:
debug("IMDb ID not valid")
return None
try:
movie = models.Movie.byImdbId(imdbId)
except models.Movie.DoesNotExist:
#this shound not happen, just in case previous imports failed
try:
imdb = models.MovieImdb.objects.get(imdbId=imdbId)
except models.MovieImdb.DoesNotExist:
imdb = models.MovieImdb()
imdb.imdbId = imdbId
imdb.save()
movie = models.Movie()
movie.imdb = imdb
info = oxweb.imdb.getMovieInfo(imdbId)
for key in ('title',
'tagline',
'year',
'release_date',
'rating',
'votes',
'series_imdb',
'season',
'episode'):
if key in info:
setattr(movie.imdb, key, info[key])
debug(key, info[key])
_info_map = {
'episode title': 'episode_title',
'series title': 'series_title',
}
for key in _info_map.keys():
if key in info:
setattr(movie.imdb, _info_map.get(key, key), info[key])
movie.imdb.plot = oxweb.imdb.getMoviePlot(imdbId)
debug("plot", movie.imdb.plot)
movie.imdb.runtime = oxweb.imdb.getMovieRuntimeSeconds(imdbId)
business = oxweb.imdb.getMovieBusinessSum(imdbId)
for key in ('gross', 'profit', 'budget'):
setattr(movie.imdb, key, business[key])
movie.imdb.save()
movie.oxdbId = "__init__%s" % random.randint(0, 100000)
movie.save()
models.AlternativeTitle.objects.filter(movie=movie, manual=False).delete()
for i in oxweb.imdb.getMovieAKATitles(imdbId):
t = models.AlternativeTitle()
t.movie = movie
t.title = i[0]
t.type = i[1]
t.save()
#FIXME: related tables should be cleaned to not accumulate cruft
#Country
models.MovieCountry.objects.filter(movie=movie, manual=False).delete()
position = 0
if 'country' in info:
for i in info['country']:
debug("add country", i)
country, created = models.Country.objects.get_or_create(name=i)
models.MovieCountry.link(movie, country, position)
position += 1
#Language
models.MovieLanguage.objects.filter(movie=movie, manual=False).delete()
position = 0
if 'language' in info:
for i in info['language']:
debug("add language", i)
language, created = models.Language.objects.get_or_create(name=i)
models.MovieLanguage.link(movie, language, position)
position += 1
#Location
movie.locations_all.filter(manual=False).delete()
locations = oxweb.imdb.getMovieLocations(imdbId)
for i in locations:
debug("add location", i)
location, created = models.Location.objects.get_or_create(name=i)
location.movies.add(movie)
#Genre
movie.genres_all.filter(manual=False).delete()
if 'genre' in info:
for i in info['genre']:
debug("add genre", i)
genre, created = models.Genre.objects.get_or_create(name=i)
genre.movies.add(movie)
#Keyword
movie.keywords_all.filter(manual=False).delete()
keywords = oxweb.imdb.getMovieKeywords(imdbId)
for g in keywords:
debug("add keyword", g)
keyword, created = models.Keyword.objects.get_or_create(name=g)
keyword.movies.add(movie)
movie.trivia_all.filter(manual=False).delete()
position = 0
trivia = oxweb.imdb.getMovieTrivia(imdbId)
for i in trivia:
debug("add trivia", i)
t = models.Trivia()
t.movie = movie
t.trivia = i
t.position = position
t.save()
position += 1
position = 0
models.Cast.objects.filter(movie=movie).filter(manual=False).delete()
credits = oxweb.imdb.getMovieCredits(imdbId)
for role in credits:
for p in credits[role]:
name = stripTags(p[0])
imdb_id = findRe(p[0], 'nm(\d{7})')
debug("add cast", name)
#FIXME: we could save character information here
character = stripTags(p[1])
person = models.Person.get_or_create(name, imdb_id)
models.Cast.link(movie, person, role, character, position)
position += 1
movie.connections_all.filter(manual=False).delete()
connections = oxweb.imdb.getMovieConnections(imdbId)
for relation in connections:
for otherId in connections[relation]:
try:
object = models.Movie.objects.get(imdb__imdbId=otherId)
debug("add connection", relation, object)
models.Connection.get_or_create(movie, relation, object)
except models.Movie.DoesNotExist:
pass
reviews = oxweb.imdb.getMovieExternalReviews(imdbId)
movie.reviews_all.filter(manual=False).delete()
for r in reviews:
debug("add review", r)
review = models.Review.get_or_create(movie, r)
review.title = reviews[r]
review.save()
movie.oxdbId = movie.oxid()
movie.save()
return movie

View file

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os
from os.path import join, dirname, basename, splitext, exists
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from ... import daemon
class Command(BaseCommand):
"""
listen to rabbitmq and execute background task.
"""
help = 'listen to rabbitmq and execute background task.'
args = ''
def handle(self, **options):
daemon.run()

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os
from os.path import join, dirname, basename, splitext, exists
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from ... import encoder
class Command(BaseCommand):
"""
listen to rabbitmq and execute encoding tasks.
"""
help = 'listen to rabbitmq and execute encoding tasks.'
args = ''
def handle(self, **options):
encoder.run()

158
pandora/backend/managers.py Normal file
View file

@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import re
from datetime import datetime
from urllib2 import unquote
import json
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q, Manager
import models
def keyType(key):
if key in ('released'):
return "date"
if key in ('year', 'cast.length'):
return "int"
if key in ('rating', 'votes'):
return "float"
return "string"
class MovieManager(Manager):
def get_query_set(self):
return super(MovieManager, self).get_query_set()
def find(self, request):
'''
construct query set from q value in request,
also checks for lists.
range and order must be applied later
'''
'''
q = ''
for i in request.META['QUERY_STRING'].split('&'):
if i.startswith('q='):
q = i[2:]
'''
q = json.loads(request.POST['data'])['q']
print q
op = ','
if '|' in q:
op = '|'
conditions = []
for e in q.split(op):
e = e.split(':')
if len(e) == 1: e = ['all'] + e
k, v = e
exclude = False
if v.startswith('!'):
v = v[1:]
exclude = True
if keyType(k) == "string":
startswith = v.startswith('^')
endswith = v.endswith('$')
if startswith and endswith:
v = v[1:-1]
k = '%s__iexact' % k
elif startswith:
v = v[1:]
k = '%s__istartswith' % k
elif v.endswith('$'):
v = v[:-1]
k = '%s__iendswith' % k
else:
k = '%s__icontains' % k
k = 'find__%s' % k
v = unquote(v)
if exclude:
conditions.append(~Q(**{k:v}))
else:
conditions.append(Q(**{k:v}))
else:
def parseDate(d):
while len(d) < 3:
d.append(1)
return datetime(*[int(i) for i in d])
#1960-1970
match = re.compile("(-?[\d\.]+?)-(-?[\d\.]+$)").findall(v)
if match:
v1 = match[0][0]
v2 = match[0][1]
if keyType(k) == "date":
v1 = parseDate(v1.split('.'))
v2 = parseDate(v2.split('.'))
if exclude: #!1960-1970
k1 = str('%s__lt' % k)
k2 = str('%s__gte' % k)
conditions.append(Q(**{k1:v1})|Q(**{k2:v2}))
else: #1960-1970
k1 = str('%s__gte' % k)
k2 = str('%s__lt' % k)
conditions.append(Q(**{k1:v1})&Q(**{k2:v2}))
else:
if keyType(k) == "date":
v = parseDate(v.split('.'))
k = str('%s' % k)
if exclude: #!1960
conditions.append(~Q(**{k:v}))
else: #1960
conditions.append(Q(**{k:v}))
#join query with operator
qs = self.get_query_set()
#only include movies that have hard metadata
qs = qs.filter(available=True)
if conditions:
q = conditions[0]
for c in conditions[1:]:
if op == '|':
q = q | c
else:
q = q & c
qs = qs.filter(q)
# filter list, works for own or public lists
l = request.GET.get('l', 'all')
if l != "all":
l = l.split(":")
only_public = True
if not request.user.is_anonymous():
if len(l) == 1: l = [request.user.username] + l
if request.user.username == l[0]:
only_public = False
if len(l) == 2:
lqs = models.List.objects.filter(name=l[1], user__username=l[0])
if only_public:
lqs = qls.filter(public=True)
if lqs.count() == 1:
qs = qs.filter(listitem__list__id=lqs[0].id)
return qs
class FileManager(Manager):
def get_query_set(self):
return super(FileManager, self).get_query_set()
def movie_files(self, movie):
q = self.get_query_set()
return q.filter(type=1, movie=movie)
class ArchiveFileManager(Manager):
def get_query_set(self):
return super(ArchiveFileManager, self).get_query_set()
def movie_files(self, movie):
q = self.get_query_set()
return q.filter(file__is_video=True, file__movie=movie)
def by_oshash(self, oshash):
q = self.get_query_set()
q = q.filter(file__oshash=oshash)
if q.count() == 0:
raise models.ArchiveFile.DoesNotExist("%s matching oshash %s does not exist." %
(models.ArchiveFile._meta.object_name, oshash))
else:
return q[0]

View file

1268
pandora/backend/models.py Normal file

File diff suppressed because it is too large Load diff

11
pandora/backend/urls.py Normal file
View file

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.conf.urls.defaults import *
urlpatterns = patterns("backend.views",
(r'^upload/$', 'firefogg_upload'),
(r'^$', 'api'),
)

127
pandora/backend/utils.py Normal file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
#
import errno
import os
import sys
import re
import hashlib
import oxlib
import oxlib.iso
from oxlib.normalize import normalizeName
def oxid(title, director, year='', seriesTitle='', episodeTitle='', season=0, episode=0):
oxid_value = u"\n".join([title, director, year])
oxid = hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()
if seriesTitle:
oxid_value = u"\n".join([seriesTitle, "%02d" % season])
oxid = hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()[:20]
oxid_value = u"\n".join(["%02d" % episode, episodeTitle, director, year])
oxid += hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()[:20]
return u"0x" + oxid
def oxdb_director(director):
director = os.path.basename(os.path.dirname(director))
if director.endswith('_'):
director = "%s." % director[:-1]
director = ", ".join([normalizeName(d) for d in director.split('; ')])
director = director.replace('Series', '')
director = director.replace('Unknown Director', '')
director = director.replace('Various Directors', '')
return director
def oxdb_title(_title, searchTitle = False):
'''
normalize filename to get movie title
'''
_title = os.path.basename(_title)
_title = _title.replace('... ', '_dot_dot_dot_')
_title = _title.replace('. ', '_dot__space_')
_title = _title.replace(' .', '_space__dot_')
title = _title.split('.')[0]
title = re.sub('([a-z0-9])_ ', '\\1: ', title)
se = re.compile('Season (\d+).Episode (\d+)').findall(_title)
if se:
se = "S%02dE%02d" % (int(se[0][0]), int(se[0][1]))
if 'Part' in _title.split('.')[-2] and 'Episode' not in _title.split('.')[-3]:
stitle = _title.split('.')[-3]
else:
stitle = _title.split('.')[-2]
if stitle.startswith('Episode '):
stitle = ''
if searchTitle:
title = '"%s" %s' % (title, stitle)
else:
title = '%s (%s) %s' % (title, se, stitle)
title = title.strip()
title = title.replace('_dot_dot_dot_', '... ')
title = title.replace('_dot__space_', '. ')
title = title.replace('_space__dot_', ' .')
return title
def oxdb_year(data):
return oxlib.findRe(data, '\.(\d{4})\.')
def oxdb_series_title(path):
seriesTitle = u''
if path.startswith('Series'):
seriesTitle = os.path.basename(os.path.dirname(path))
else:
t = oxdb_title(path)
if " (S" in t:
seriesTitle = t.split(" (S")[0]
return seriesTitle
def oxdb_episode_title(path):
episodeTitle = u''
ep = re.compile('.Episode \d+?\.(.*?)\.[a-zA-Z]').findall(path)
if ep:
episodeTitle = ep[0]
return episodeTitle
def oxdb_season_episode(path):
season = 0
episode = 0
path = os.path.basename(path)
se = re.compile('Season (\d+).Episode (\d+)').findall(path)
if se:
season = int(se[0][0])
episode = int(se[0][1])
else:
ep = re.compile('.Episode (\d+?)').findall(path)
if ep:
episode = int(ep[0][0])
if season == 0 and episode == 0:
se = re.compile('S(\d\d)E(\d\d)').findall(path)
if se:
season = int(se[0][0])
episode = int(se[0][1])
return (season, episode)
def oxdb_part(path):
part = 1
path = path.lower()
p = re.compile('part\s*?(\d+)\.').findall(path)
if p:
part = p[0]
else:
p = re.compile('cd\s*?(\d+)\.').findall(path)
if p:
part = p[0]
return part
def parsePath(path):
import oxweb.imdb
search_title = oxdb_title(path, True)
r = {}
r['title'] = oxdb_title(path)
r['director'] = oxdb_director(path)
r['episode_title'] = oxdb_episode_title(path)
r['season'], r['episode'] = oxdb_season_episode(path)
r['series'] = oxdb_series_title(path)
r['part'] = oxdb_part(path)
r['imdbId'] = oxweb.imdb.guess(search_title, r['director'], timeout=-1)
return r

706
pandora/backend/views.py Normal file
View file

@ -0,0 +1,706 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os.path
import re
from datetime import datetime
from urllib2 import unquote
import json
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
from django.http import HttpResponse
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404
from django.template import RequestContext
from django.conf import settings
try:
import simplejson as json
except ImportError:
from django.utils import simplejson as json
from oxdjango.decorators import login_required_json
from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response
import models
import utils
from daemon import send_bg_message
from oxuser.models import getUserJSON
from oxuser.views import api_login, api_logout, api_register, api_contact, api_recover, api_preferences
def api(request):
if request.META['REQUEST_METHOD'] == "OPTIONS":
response = HttpResponse('')
#response = render_to_json_response({'status': {'code': 200, 'text': 'please use POST'}})
response['Access-Control-Allow-Origin'] = '*'
return response
if not 'action' in request.POST:
return apidoc(request)
function = request.POST['action']
#FIXME: possible to do this in f
#data = json.loads(request.POST['data'])
#FIXME: security considerations, web facing api should not call anything in globals!!!
f = globals().get('api_'+function, None)
if f:
response = f(request)
else:
response = render_to_json_response(json_response(status=400,
text='Unknown function %s' % function))
#response['Access-Control-Allow-Origin'] = '*'
return response
def api_hello(request):
'''
return {'status': {'code': int, 'text': string},
'data': {user: object}}
'''
response = json_response({})
if request.user.is_authenticated():
response['data']['user'] = getUserJSON(request.user)
else:
response['data']['user'] = {'name': 'Guest', 'group': 'guest', 'preferences': {}}
return render_to_json_response(response)
def api_error(request):
'''
trows 503 error
'''
success = error_is_success
return render_to_json_response({})
def _order_query(qs, s, prefix='sort__'):
order_by = []
for e in s.split(','):
o = e.split(':')
if len(o) == 1: o.append('asc')
order = {'id': 'movieId'}.get(o[0], o[0])
order = '%s%s' % (prefix, order)
if o[1] == 'desc':
order = '-%s' % order
order_by.append(order)
if order_by:
qs = qs.order_by(*order_by)
return qs
def _parse_query(request):
get = json.loads(request.POST['data'])
query = {}
query['i'] = 0
query['o'] = 100
query['s'] = 'title:asc'
def parse_dict(s):
d = s.split(",")
return [i.strip() for i in d]
_dicts = ['p', ]
_ints = ['n', ]
for key in ('s', 'p', 'g', 'l', 'n'):
if key in get:
if key in _ints:
query[key] = int(get[key])
elif key in _dicts:
query[key] = parse_dict(get[key])
else:
query[key] = get[key]
query['q'] = models.Movie.objects.find(request)
if 'r' in get:
r = get['r'].split(':')
if len(r) == 1: r.append(0)
if r[0] == '': r[0] = 0
if r[1] == '': r[0] = -1
query['i'] = int(r[0])
query['o'] = int(r[1])
#group by only allows sorting by name or number of itmes
return query
def api_find(request):
'''
param data
{'q': query, 's': sort, 'r': range}
q: query string, can contain field:search more on query syntax at
http://wiki.0xdb.org/wiki/QuerySyntax
s: comma seperated list of field:order, default: director:asc,year:desc
r: result ragne, from:to or from
p: properties to return, if obmited stats are returned
g: group elements by, country, genre, director...
with p, items is list of dicts with requested properties:
return {'status': {'code': int, 'text': string},
'data': {items: array}}
with g, items contains list of {'title': string, 'items': int}:
return {'status': {'code': int, 'text': string},
'data': {items: array}}
with g + n=1, return number of items in given query
return {'status': {'code': int, 'text': string},
'data': {items: int}}
without p or g, return stats about query:
return {'status': {'code': int, 'text': string},
'data': {items=int, files=int, pixels=int, size=int, duration=int}}
'''
query = _parse_query(request)
response = json_response({})
if 'p' in query:
response['data']['items'] = []
qs = _order_query(query['q'], query['s'])
if 'n' in query:
response = {'items': qs.count()}
else:
_p = query['p']
def only_p(m):
r = {}
if m:
m = json.loads(m)
for p in _p:
r[p] = m[p]
return r
qs = qs[query['i']:query['o']]
response['data']['items'] = [only_p(m['json']) for m in qs.values('json')]
elif 'g' in query:
if query['s'].split(':')[0] not in ('name', 'items'):
query['s'] = 'name'
#FIXME: also filter lists here
response['data']['items'] = []
name = 'name'
items = 'movies'
movie_qs = query['q']
_objects = {
'country': models.Country.objects,
'genre': models.Genre.objects,
'language': models.Language.objects,
'director': models.Person.objects.filter(cast__role='directors'),
}
if query['g'] in _objects:
qs = _objects[query['g']].filter(movies__id__in=movie_qs).values('name').annotate(movies=Count('movies'))
elif query['g'] == "year":
qs = movie_qs.values('imdb__year').annotate(movies=Count('id'))
name='imdb__year'
if 'n' in query:
response['data']['items'] = qs.count()
else:
#replace normalized items/name sort with actual db value
order_by = query['s'].split(":")
if len(order_by) == 1:
order_by.append('desc')
if order_by[0] == 'name':
order_by = "%s:%s" % (name, order_by[1])
else:
order_by = "%s:%s" % (items, order_by[1])
qs = _order_query(qs, order_by, '')
qs = qs[query['i']:query['o']]
response['data']['items'] = [{'title': i[name], 'items': i[items]} for i in qs]
else:
#FIXME: also filter lists here
movies = models.Movie.objects.filter(available=True)
files = models.File.objects.all()
response['data']['items'] = movies.count()
response['data']['files'] = files.count()
r = files.aggregate(Count('size'), Count('pixels'), Count('duration'))
response['data']['pixels'] = r['pixels__count']
response['data']['size'] = r['size__count']
response['data']['duration'] = r['duration__count']
return render_to_json_response(response)
def api_getItem(request):
'''
param data
string id
return item array
'''
response = json_response({})
itemId = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Movie, movieId=itemId)
#FIXME: check permissions
response['data'] = {'item': item.json}
return render_to_json_response(response)
@login_required_json
def api_editItem(request):
'''
param data
{id: string, key: value,..}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
data = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Movie, movieId=data['id'])
if item.editable(request.user):
response = json_response(status=501, text='not implemented')
item.edit(data)
else:
response = json_response(status=403, text='permissino denied')
return render_to_json_response(response)
@login_required_json
def api_removeItem(request):
'''
param data
string id
return {'status': {'code': int, 'text': string}}
'''
response = json_response({})
itemId = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Movie, movieId=itemId)
if item.editable(request.user):
response = json_response(status=501, text='not implemented')
else:
response = json_response(status=403, text='permissino denied')
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)
@login_required_json
def api_addListItem(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_removeListItem(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_addList(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_editList(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
def api_removeList(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_addArchive(request):
'''
ARCHIVE API NEEDS CLEANUP
param data
{name: string}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
data = json.loads(request.POST['data'])
try:
archive = models.Archive.objects.get(name=data['name'])
response = {'status': {'code': 401, 'text': 'archive with this name exists'}}
except models.Archive.DoesNotExist:
archive = models.Archive(name=data['name'])
archive.save()
archive.users.add(request.user)
response = json_response({})
response['status']['text'] = 'archive created'
return render_to_json_response(response)
@login_required_json
def api_editArchive(request):
'''
ARCHIVE API NEEDS CLEANUP
param data
{id: string, key: value,..}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
data = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Archive, name=data['name'])
if item.editable(request.user):
response = json_response(status=501, text='not implemented')
item.edit(data)
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
@login_required_json
def api_removeArchive(request):
'''
ARCHIVE API NEEDS CLEANUP
param data
string id
return {'status': {'code': int, 'text': string}}
'''
response = json_response({})
itemId = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Archive, movieId=itemId)
if item.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)
#@login_required_json
def api_update(request):
'''
param data
{archive: string, files: json}
return {'status': {'code': int, 'text': string},
'data': {info: object, rename: object}}
'''
data = json.loads(request.POST['data'])
archive = data['archive']
files = data['files']
archive = get_object_or_404_json(models.Archive, name=archive)
if archive.editable(request.user):
needs_data = []
rename = {}
for oshash in files:
data = files[oshash]
q = models.ArchiveFile.objects.filter(archive=archive, file__oshash=oshash)
if q.count() == 0:
#print "adding file", oshash, data['path']
f = models.ArchiveFile.get_or_create(archive, oshash)
f.update(data)
if not f.file.movie:
send_bg_message({'findMovie': f.file.id})
#FIXME: only add if it was not in File
else:
f = q[0]
if data['path'] != f.path:
f.path = data['path']
f.save()
if f.file.needs_data:
needs_data.append(oshash)
if f.path != f.file.path:
rename[oshash] = f.file.path
#print "processed files for", archive.name
#remove all files not in files.keys() from ArchiveFile
response = json_response({'info': needs_data, 'rename': rename})
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
def api_encodingSettings(request):
'''
returns Firefogg encoding settings as specified by site
return {'status': {'code': int, 'text': string},
'data': {'options': {'videoQuality':...}}}
'''
response = json_response({'options': settings.VIDEO_ENCODING[settings.VIDEO_PROFILE]})
return render_to_json_response(response)
class UploadForm(forms.Form):
data = forms.TextInput()
file = forms.FileField()
class VideoChunkForm(forms.Form):
chunk = forms.FileField()
done = forms.IntegerField(required=False)
@login_required_json
def api_upload(request): #video, timeline, frame
'''
upload video, timeline or frame
param data
param file
return {'status': {'code': int, 'text': string},
'data': {}}
'''
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
data = json.loads(request.POST['data'])
oshash = data['oshash']
f = get_object_or_404(models.File, oshash=oshash)
if data['item'] == 'frame':
ff = form.cleaned_data['file']
position = data['position']
frame, created = models.Frame.objects.get_or_create(file=f, position=position)
if not created and frame.frame:
frame.frame.delete()
frame.frame.save(ff.name, ff)
frame.save()
response = json_response({'url': frame.frame.url})
return render_to_json_response(response)
if data['item'] == 'timeline':
pass
#print "not implemented"
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def firefogg_upload(request):
#handle video upload
if request.method == 'POST':
#init upload
if 'oshash' in request.POST:
#FIXME: what to do if requested oshash is not in db?
#FIXME: should existing data be reset here? or better, should this fail if an upload was there
f = get_object_or_404(models.File, oshash=request.POST['oshash'])
stream = getattr(f, 'stream_%s'%settings.VIDEO_UPLOAD)
if stream:
stream.delete()
f.available = False
f.save()
response = {
'uploadUrl': request.build_absolute_uri('/api/upload/?oshash=%s' % f.oshash),
'result': 1
}
return render_to_json_response(response)
#post next chunk
if 'chunk' in request.FILES and 'oshash' in request.GET:
print "all chunk now"
f = get_object_or_404(models.File, oshash=request.GET['oshash'])
form = VideoChunkForm(request.POST, request.FILES)
#FIXME:
if form.is_valid() and f.editable(request.user):
c = form.cleaned_data['chunk']
response = {
'result': 1,
'resultUrl': request.build_absolute_uri('/')
}
if not f.save_chunk(c, c.name):
response['result'] = -1
elif form.cleaned_data['done']:
#FIXME: send message to encode deamon to create derivates instead
f.available = True
f.save()
response['result'] = 1
response['done'] = 1
return render_to_json_response(response)
print request.GET, request.POST
response = json_response(status=400, text='this request requires POST')
return render_to_json_response(response)
@login_required_json
def api_editFile(request): #FIXME: should this be file.files. or part of update
'''
change file / imdb link
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
def api_parse(request): #parse path and return info
'''
param data
{path: string}
return {'status': {'code': int, 'text': string},
data: {imdb: string}}
'''
path = json.loads(request.POST['data'])['path']
response = json_response(utils.parsePath(path))
return render_to_json_response(response)
def api_getImdbId(request):
'''
param data
{title: string, director: string, year: string}
return {'status': {'code': int, 'text': string},
'data': {imdbId:string }}
'''
imdbId = oxweb.imdb.guess(search_title, r['director'], timeout=-1)
if imdbId:
response = json_response({'imdbId': imdbId})
else:
response = json_response(status=404, text='not found')
return render_to_json_response(response)
def api_fileInfo(request):
'''
param data
oshash string
return {'status': {'code': int, 'text': string},
'data': {imdbId:string }}
'''
if 'data' in request.POST:
oshash = json.loads(request.POST['data'])
elif 'oshash' in request.GET:
oshash = request.GET['oshash']
f = models.MovieFile.objects.get(oshash=oshash)
response = {'data': f.json()}
return render_to_json_response(response)
def api_subtitles(request):
'''
param data
oshash string
language string
subtitle string
return
if no language is provided:
{data: {languages: array}}
if language is set:
{data: {subtitle: string}}
if subtitle is set:
saves subtitle for given language
'''
if 'data' in request.POST:
data = json.loads(request.POST['data'])
oshash = data['oshash']
language = data.get('language', None)
srt = data.get('subtitle', None)
if srt:
user = request.user
sub = models.Subtitles.objects.get_or_create(user, oshash, language)
sub.srt = srt
sub.save()
else:
response = json_response({})
if language:
q = models.Subtitles.objects.filter(movie_file__oshash=oshash, language=language)
if q.count() > 0:
response['data']['subtitle'] = q[0].srt
return render_to_json_response(response)
l = models.Subtitles.objects.filter(movie_file__oshash=oshash).values('language')
response['data']['languages'] = [f['language'] for f in l]
return render_to_json_response(response)
'''
GET list
> {
"files": {
"a41cde31c581e11d": {"path": "E/Example, The/An Example.avi", "size":1646274},
}
}
'''
@login_required_json
def list_files(request):
response = {}
response['files'] = {}
qs = models.UserFile.filter(user=request.user)
p = Paginator(qs, 1000)
for i in p.page_range:
page = p.page(i)
for f in page.object_list:
response['files'][f.movie_file.oshash] = {'path': f.path, 'size': f.movie_file.size}
return render_to_json_response(response)
def find_files(request):
response = {}
query = _parse_query(request)
response['files'] = {}
qs = models.UserFile.filter(user=request.user).filter(movie_file__movie__id__in=query['q'])
p = Paginator(qs, 1000)
for i in p.page_range:
page = p.page(i)
for f in page.object_list:
response['files'][f.movie_file.oshash] = {'path': f.path, 'size': f.movie_file.size}
return render_to_json_response(response)
def apidoc(request):
'''
this is used for online documentation at http://127.0.0.1:8000/api/
'''
import sys
def trim(docstring):
if not docstring:
return ''
# Convert tabs to spaces (following the normal Python rules)
# and split into a list of lines:
lines = docstring.expandtabs().splitlines()
# Determine minimum indentation (first line doesn't count):
indent = sys.maxint
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
# Remove indentation (first line is special):
trimmed = [lines[0].strip()]
if indent < sys.maxint:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
# Strip off trailing and leading blank lines:
while trimmed and not trimmed[-1]:
trimmed.pop()
while trimmed and not trimmed[0]:
trimmed.pop(0)
# Return a single string:
return '\n'.join(trimmed)
functions = filter(lambda x: x.startswith('api_'), globals().keys())
api = []
for f in sorted(functions):
api.append({
'name': f[4:],
'doc': trim(globals()[f].__doc__).replace('\n', '<br>\n')
})
context = RequestContext(request, {'api': api,
'sitename': settings.SITENAME,})
return render_to_response('api.html', context)

11
pandora/manage.py Normal file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)

View file

68
pandora/oxuser/models.py Normal file
View file

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.contrib.auth.models import User
from django.db import models
from django.db.models import signals
from django.dispatch import dispatcher
from django.utils import simplejson as json
class UserProfile(models.Model):
recover_key = models.TextField()
user = models.ForeignKey(User, unique=True)
def user_post_save(sender, instance, **kwargs):
profile, new = UserProfile.objects.get_or_create(user=instance)
models.signals.post_save.connect(user_post_save, sender=User)
class Preference(models.Model):
user = models.ForeignKey(User, related_name='preferences')
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
key = models.CharField(blank=True, max_length=255)
value = models.TextField(blank=True)
def getUserJSON(user):
json = {}
for key in ('username', ):
json[key] = getattr(user, key)
json['group'] = 'user'
if user.is_staff:
json['group'] = 'admin'
elif user.has_perm('0x.vip'): #FIXME: permissions
json['group'] = 'vip'
json['preferences'] = getPreferences(user)
return json
def getPreferences(user):
prefs = {}
for p in Preference.objects.filter(user=user):
prefs[p.key] = json.loads(p.value)
prefs['email'] = user.email
return prefs
def getPreference(user, key, value=None):
if key in ('email', ):
value = getattr(user, key)
else:
q = Preference.objects.filter(user=user, key=key)
if q.count() > 0:
value = json.loads(q[0].value)
return value
def setPreference(user, key, value):
if key in ('email', ):
setattr(user, key, value)
user.save()
else:
value = json.dumps(value)
q = Preference.objects.filter(user=user, key=key)
if q.count() > 0:
p = q[0]
p.value = value
p.save()
else:
p = Preference(user=user, key=key, value=value)
p.save()

View file

@ -0,0 +1,10 @@
Somebody requested a new password for your {{sitename}} account.
If it is you, and if you forgot your password, you can login
by clicking the following url:
{{recover_url}}
If you did not request a password, you can safely ignore this e-mail.
{{sitename}}

23
pandora/oxuser/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
"""}

14
pandora/oxuser/urls.py Normal file
View file

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.conf.urls.defaults import *
urlpatterns = patterns("oxdb.oxuser.views",
(r'^preferences', 'api_preferences'),
(r'^login', 'api_login'),
(r'^logout', 'api_logout'),
(r'^register', 'api_register'),
(r'^recover', 'api_recover'),
)

226
pandora/oxuser/views.py Normal file
View file

@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import uuid
import hashlib
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404, redirect
from django.template import RequestContext, loader, Context
from django.utils import simplejson as json
from django.conf import settings
from django.core.mail import send_mail, BadHeaderError
from oxdjango.shortcuts import render_to_json_response, json_response
from oxdjango.decorators import login_required_json
import models
class LoginForm(forms.Form):
username = forms.TextInput()
password = forms.TextInput()
def api_login(request):
'''
param data
{'username': username, 'password': password}
return {'status': {'code': int, 'text': string}}
'''
response = json_response(status=403, text='login failed')
data = json.loads(request.POST['data'])
form = LoginForm(data, request.FILES)
if form.is_valid():
user = authenticate(username=data['username'], password=data['password'])
if user is not None:
if user.is_active:
login(request, user)
user_json = models.getUserJSON(user)
response = json_response({'user': user_json},
text='You are logged in.')
else:
response = json_response(status=401,
text='Your account is disabled.')
else:
errors = json_errors(form)
response = json_response(errors,
status=401, text='Your username and password were incorrect.')
else:
response = json_response(status=400, text='invalid data')
return render_to_json_response(response)
def api_logout(request):
'''
param data
{}
return {'status': {'code': int, 'text': string}}
'''
response = json_response(text='logged out')
if request.user.is_authenticated():
logout(request)
return render_to_json_response(response)
class RegisterForm(forms.Form):
username = forms.TextInput()
password = forms.TextInput()
email = forms.TextInput()
def api_register(request):
'''
param data
{'username': username, 'password': password, 'email': email}
return {'status': {'code': int, 'text': string}}
'''
data = json.loads(request.POST['data'])
form = RegisterForm(data, request.FILES)
if form.is_valid():
if models.User.objects.filter(username=form.data['username']).count() > 0:
response = json_response(status=400, text='username or email exists')
elif models.User.objects.filter(email=form.data['email']).count() > 0:
response = json_response(status=400, text='username or email exists')
else:
user = models.User(username=form.data['username'], email=form.data['email'])
user.set_password(form.data['password'])
user.save()
user = authenticate(username=form.data['username'],
password=form.data['password'])
login(request, user)
response = json_response(text='account created')
else:
response = json_response(status=400, text='username exists')
return render_to_json_response(response)
class RecoverForm(forms.Form):
username_or_email = forms.TextInput()
def api_recover(request):
'''
param data
{'username_or_email': username}
return {'status': {'code': int, 'text': string}}
'''
data = json.loads(request.POST['data'])
form = RegisterForm(data, request.FILES)
if form.is_valid():
username = data['username_or_email']
user = None
q = models.User.objects.filter(username=username)
if q.count() > 0:
user = q[0]
else:
q = models.User.objects.filter(email=username)
if q.count() > 0:
user = q[0]
if user:
key = hashlib.sha1(str(uuid.uuid4())).hexdigest()
user_profile = user.get_profile()
user_profile.recover_key = key
user_profile.save()
template = loader.get_template('recover_email.txt')
context = RequestContext(request, {
'recover_url': request.build_absolute_uri("/r/%s" % key),
'sitename': settings.SITENAME,
})
message = template.render(context)
subject = '%s account recovery' % settings.SITENAME
user.email_user(subject, message)
response = json_response(text='recover email sent')
else:
response = json_response(status=404, text='username or email not found')
else:
response = json_response(status=400, text='invalid data')
return render_to_json_response(response)
def recover(request, key):
qs = models.UserProfile.objects.filter(recover_key=key)
if qs.count() == 1:
user = qs[0].user
user.set_password(key)
user.save()
user_profile = user.get_profile()
user_profile.recover_key = ''
user_profile.save()
user = authenticate(username=user.username, password=key)
login(request, user)
#FIXME: set message to notify user to update password
return redirect('/#settings')
return redirect('/')
class ContactForm(forms.Form):
email = forms.EmailField()
subject = forms.TextInput()
message = forms.TextInput()
def api_contact(request):
'''
param data
{'email': string, 'message': string}
return {'status': {'code': int, 'text': string}}
'''
data = json.loads(request.POST['data'])
form = ContactForm(data, request.FILES)
if form.is_valid():
email = data['email']
template = loader.get_template('contact_email.txt')
context = RequestContext(request, {
'sitename': settings.SITENAME,
'email': email,
'message': data['message'],
})
message = template.render(context)
subject = '%s contact: %s' % (settings.SITENAME, data['subject'])
response = json_response(text='message sent')
try:
send_mail(subject, message, email, [settings.DEFAULT_FROM_EMAIL, ])
except BadHeaderError:
response = json_response(status=400, text='invalid data')
else:
response = json_response(status=400, text='invalid data')
return render_to_json_response(response)
@login_required_json
def api_preferences(request):
'''
param data
string
array
object
return
if data is empy or {}
if data is string:
return preference with name
if data is array:
return preferences with names
if data is object:
set key values in dict as preferences
'''
response = json_response()
if 'data' not in request.POST:
response['data']['preferences'] = models.getPreferences(request.user)
else:
data = json.loads(request.POST['data'])
if isinstance(data, basestring):
response['data']['preferences'] = {}
response['data']['preferences'][data] = models.getPreference(request.user, data)
elif isinstance(data, list):
response['data']['preferences'] = {}
for preference in data:
response['preferences'][preference] = models.getPreference(request.user, preference)
elif isinstance(data, dict):
if not data:
response['data']['preferences'] = models.getPreferences(request.user)
else:
del response['data']
for key in data:
models.setPreference(request.user, key, data[key])
return render_to_json_response(response)

147
pandora/settings.py Normal file
View file

@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
# Django settings for pandora project.
import os
from os.path import join
SITENAME = 'Pandora'
URL = 'pandora.Oxdb.org'
PROJECT_ROOT = os.path.normpath(os.path.dirname(__file__))
DEBUG = True
TEMPLATE_DEBUG = DEBUG
JSON_DEBUG = False
ADMINS = (
('j', 'j@mailb.org'),
)
DEFAULT_FROM_EMAIL='bot@0xdb.org'
MANAGERS = ADMINS
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = 'dev.sqlite' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
#CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
#rabbitmq connection settings
BROKER_HOST = "127.0.0.1"
BROKER_PORT = 5672
BROKER_USER = "pandora"
BROKER_PASSWORD = "box"
BROKER_VHOST = "/pandora"
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'Europe/Berlin'
TIME_ZONE = 'Asia/Kolkata'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
APPEND_SLASH = False
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = join(PROJECT_ROOT, 'media')
STATIC_ROOT = join(PROJECT_ROOT, 'static')
TESTS_ROOT = join(PROJECT_ROOT, 'tests')
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
STATIC_URL = '/static/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/admin/media/'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
'django.template.loaders.eggs.load_template_source',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'oxdjango.middleware.ExceptionMiddleware',
)
ROOT_URLCONF = 'pandora.urls'
TEMPLATE_DIRS = (
join(PROJECT_ROOT, 'templates'),
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.humanize',
'django_extensions',
'devserver',
# 'south',
'app',
'backend',
'oxuser',
)
AUTH_PROFILE_MODULE = 'oxuser.UserProfile'
#Video encoding settings
VIDEO_PROFILE = 'low' # possible values low, mid or high
VIDEO_ENCODING = {
'low': {'height': 96, 'videoBitrate': 180, 'softTarget': True,
'samplerate': 44100, 'audioQuality': -1, 'channels': 1, 'noUpscaling': True},
'mid': {'maxSize': 320, 'videoBitrate': 500,
'samplerate': 44100, 'audioQuality': 0, 'channels': 1, 'noUpscaling': True},
'high': {'profile': 'padma'}
}
#overwrite default settings with local settings
try:
from local_settings import *
except ImportError:
pass
# Make this unique, creates random key first at first time.
try:
SECRET_KEY
except NameError:
SECRET_FILE = os.path.join(PROJECT_ROOT, 'secret.txt')
try:
SECRET_KEY = open(SECRET_FILE).read().strip()
except IOError:
try:
from random import choice
SECRET_KEY = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
secret = file(SECRET_FILE, 'w')
secret.write(SECRET_KEY)
secret.close()
except IOError:
Exception('Please create a %s file with random characters to generate your secret key!' % SECRET_FILE)

37
pandora/static/css/ui.css Normal file
View file

@ -0,0 +1,37 @@
body {
background: rgb(16, 16, 16);
}
div {
-moz-user-select: none;
-webkit-user-select: none;
}
input#find {
position: absolute;
top: 4px;
right: 24px;
width: 240px;
}
img#loading {
position: absolute;
top: 4px;
right: 4px;
width: 16px;
height: 16px;
display: hidden;
}
#sideBrowserPlayer {
text-align: center;
}
#statusBar {
-moz-box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.75);
-webkit-box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.75);
}
.OxCell.OxColumnTitle {
//font-weight: bold;
}

1
pandora/static/jquery Symbolic link
View file

@ -0,0 +1 @@
js/jquery/

View file

@ -0,0 +1,91 @@
ajax_filtered_fields = {
request_url: "/ajax_filtered_fields/json_index/",
data_loaded: "data_loaded",
_appendOption: function(obj, selector) {
// append a json data row as an option to the selector
var option = $('<option>' + obj[1] + '</option>');
option.attr({value: obj[0]});
option.appendTo(selector);
return option;
},
_removeOptions: function(selector) {
// remove all options from selector
selector.children("option").each(function(i) {
$(this).remove();
});
},
getManyToManyJSON: function(element_id, app_label, object_name,
lookup_string, select_related) {
// manage the ManyToMany ajax request
var selector_from = $("#" + element_id + "_from");
var selector_to = $("#" + element_id + "_to");
$("#" + element_id + "_input").val("");
selector_from.attr("disabled", true);
selector_to.attr("disabled", true);
this._removeOptions(selector_from);
$.getJSON(this.request_url, {
app_label: app_label,
object_name: object_name,
lookup_string: lookup_string,
select_related: select_related},
function(data){
$.each(data, function(i, obj){
var option_is_selected = selector_to.children("option[value='" + obj[0] + "']").length;
if (!option_is_selected) {
ajax_filtered_fields._appendOption(obj, selector_from);
};
});
SelectBox.init(element_id + "_from");
selector_from.attr("disabled", false);
selector_to.attr("disabled", false);
selector_from.trigger(ajax_filtered_fields.data_loaded);
});
},
getForeignKeyJSON: function(element_id, app_label, object_name,
lookup_string, select_related) {
// manage the ForeignKey ajax request
var selector = $("#" + element_id);
var hidden = $("#hidden-" + element_id);
$("#" + element_id + "_input").val("");
selector.attr("disabled", true);
this._removeOptions(selector);
$.getJSON(this.request_url, {
app_label: app_label,
object_name: object_name,
lookup_string: lookup_string,
select_related: select_related},
function(data){
var selection = hidden.val();
ajax_filtered_fields._appendOption(new Array("", "---------"), selector);
$.each(data, function(i, obj){
ajax_filtered_fields._appendOption(obj, selector);
});
selector.children("option[value='" + selection + "']").attr("selected", "selected");
selector.attr("disabled", false);
SelectBox.init(element_id);
ajax_filtered_fields.bindForeignKeyOptions(element_id);
selector.trigger(ajax_filtered_fields.data_loaded);
});
},
bindForeignKeyOptions: function(element_id) {
// bind the dummy options to the hidden field that do the work
var selector = $("#" + element_id);
var hidden = $("#hidden-" + element_id);
selector.change(function(e) {
hidden.val($(this).val());
});
}
};

19
pandora/static/js/jquery/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

1155
pandora/static/js/ui.js Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View file

@ -0,0 +1,2 @@
{'status': {'code': 404, 'text': 'not found'}}

View file

@ -0,0 +1 @@
500 error

View file

@ -0,0 +1,10 @@
{% extends "admin/base.html" %}
{% load i18n %}
{% block title %}{{ title }} | 0xdb.org{% endblock %}
{% block branding %}
<h1 id="site-name">0xdb.org</h1>
{% endblock %}
{% block nav-global %}{% endblock %}

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>{{sitename}} API</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript" src="/static/js/jquery/jquery.js"></script>
</head>
<body>
<h1>{{sitename}} Public API</h1>
<a href="http://code.0xdb.org/pandora/annotate/head%3A/pandora/backend/views.py">look inside</a>
<div>
{% for f in api %}
<div>
<h2><a name="{{f.name}}">action: {{f.name}}</a></h2>
<pre style="padding-left: 24px">
{{f.doc|safe}}
</pre>
</div>
{% endfor %}
</div>
</body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>{{settings.SITENAME}}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<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.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="/site.js"></script>
<script type="text/javascript" src="/static/js/oxdb.js"></script>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,8 @@
var site = {};
site.url = "{{settings.URL}}";
site.name = "{{settings.SITENAME}}";
site.pages = {};
{% for page in pages %}
site.pages['{{page.name}}'] = '{{page.body|escapejs}}';
{% endfor %}

36
pandora/urls.py Normal file
View file

@ -0,0 +1,36 @@
from django.conf.urls.defaults import *
from django.conf import settings
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Example:
(r'^ajax_filtered_fields/', include('ajax_filtered_fields.urls')),
(r'^api/', include('backend.urls')),
(r'^site.js$', 'app.views.site_js'),
(r'^$', 'app.views.index'),
(r'^r/(?P<key>.*)$', 'oxuser.views.recover'),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
(r'^admin/(.*)', admin.site.root),
)
if settings.DEBUG:
urlpatterns += patterns('',
(r'^media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT}),
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.STATIC_ROOT}),
(r'^tests/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.TESTS_ROOT}),
)