merge
59
README
|
@ -1,14 +1,16 @@
|
||||||
pan.do/ra - open media archive
|
pan.do/ra - open media archive
|
||||||
|
|
||||||
== DEVELOPMENT ==
|
== SETUP ==
|
||||||
To setup a development instance of pan.do/ra,
|
To setup pan.do/ra, you need
|
||||||
you need python, bazaar, pip and virtualenv and several other python modules:
|
python, bazaar, pip and virtualenv and several other python modules:
|
||||||
|
|
||||||
* Packages
|
* Packages
|
||||||
apt-get install bzr git subversion mercurial \
|
apt-get install bzr git subversion mercurial \
|
||||||
python-setuptools python-pip python-virtualenv ipython \
|
python-setuptools python-pip python-virtualenv ipython \
|
||||||
python-dev python-imaging python-numpy python-psycopg2
|
python-dev python-imaging python-numpy python-psycopg2
|
||||||
|
|
||||||
|
* Pan.do/ra
|
||||||
|
Get code from bazzar
|
||||||
bzr branch http://code.0x2620.org/pandora pandora
|
bzr branch http://code.0x2620.org/pandora pandora
|
||||||
cd pandora
|
cd pandora
|
||||||
virtualenv .
|
virtualenv .
|
||||||
|
@ -17,6 +19,18 @@ you need python, bazaar, pip and virtualenv and several other python modules:
|
||||||
cd static
|
cd static
|
||||||
bzr branch http://code.0x2620.org/oxjs
|
bzr branch http://code.0x2620.org/oxjs
|
||||||
|
|
||||||
|
create settings_local.py and create site.jsonc
|
||||||
|
(use settings.py / 0xdb.jsonc / padma.jsonc as example)
|
||||||
|
create db
|
||||||
|
|
||||||
|
./manage.py syncdb
|
||||||
|
|
||||||
|
create / update static files
|
||||||
|
|
||||||
|
./manage.py update_static
|
||||||
|
./manage.py compile_pyc
|
||||||
|
|
||||||
|
|
||||||
* Additional pandora tools:
|
* Additional pandora tools:
|
||||||
You need current versions of oxframe, oxtimeline installed.
|
You need current versions of oxframe, oxtimeline installed.
|
||||||
For Ubuntu we provide these packages in a ppa:
|
For Ubuntu we provide these packages in a ppa:
|
||||||
|
@ -30,11 +44,12 @@ you need python, bazaar, pip and virtualenv and several other python modules:
|
||||||
(make sure you have the python bindings installed).
|
(make sure you have the python bindings installed).
|
||||||
|
|
||||||
create a postgresql database and adjust settings in local_settings.py
|
create a postgresql database and adjust settings in local_settings.py
|
||||||
and run ./manage.py syncdb to populate the database
|
and run ./manage.py syncdb to populate the database.
|
||||||
you might want to load example configurations from fixutes.
|
|
||||||
|
|
||||||
createdb -T template0 --locale=C --encoding=UTF8 -O pandora pandora
|
createdb -T template0 --locale=C --encoding=UTF8 -O pandora pandora
|
||||||
|
|
||||||
|
(setting locale to C is required to fix a bug in sort if set to UTF8)
|
||||||
|
|
||||||
* RabbitMQ
|
* RabbitMQ
|
||||||
For background tasks we use RabbitMQ, to install rabbitmq:
|
For background tasks we use RabbitMQ, to install rabbitmq:
|
||||||
sudo apt-get install rabbitmq-server
|
sudo apt-get install rabbitmq-server
|
||||||
|
@ -48,7 +63,7 @@ you need python, bazaar, pip and virtualenv and several other python modules:
|
||||||
* H264
|
* H264
|
||||||
to support h264 videos, install ffmpeg with x264 enabled,
|
to support h264 videos, install ffmpeg with x264 enabled,
|
||||||
install qt-faststart from (ffmpeg/tools)
|
install qt-faststart from (ffmpeg/tools)
|
||||||
set VIDEO_H264 = True in local_settings.py
|
to enable add "mp4" to video.formats in your config.jsonc
|
||||||
|
|
||||||
Running developer environment:
|
Running developer environment:
|
||||||
in one terminal:
|
in one terminal:
|
||||||
|
@ -56,24 +71,36 @@ Running developer environment:
|
||||||
and in another one:
|
and in another one:
|
||||||
./manage.py celeryd -Q default,encoding -B
|
./manage.py celeryd -Q default,encoding -B
|
||||||
|
|
||||||
Updating database:
|
=== Updating ===
|
||||||
right now database updates are not managed, each time you update to current bzr
|
To update a pandora installation get the latest version from bzr by running
|
||||||
you might have to update db tables too.
|
./update.sh
|
||||||
Use
|
this will pull pandora/oxjs/python-ox and list possible upgrades to the db
|
||||||
./manage.py sqldiff appname
|
|
||||||
to check if changes exist and
|
to update your database tables, use
|
||||||
./manage.py sqldiff appname | ./manage.py dbshell
|
./manage.py sqldiff -a
|
||||||
|
to check if there are changes and
|
||||||
|
./manage.py sqldiff -a | ./manage.py dbshell
|
||||||
to apply them.
|
to apply them.
|
||||||
This has to be done for all installed apps
|
|
||||||
|
|
||||||
== DEPLOYMENT ==
|
== DEPLOYMENT ==
|
||||||
|
* Install upstart scripts
|
||||||
|
check etc/init for upstart scripts, adjust path and user and put into /etc/init
|
||||||
|
|
||||||
To run pan.do/ra in production, we use nginx, using apache2 is also possible.
|
To run pan.do/ra in production, we use nginx, using apache2 is also possible.
|
||||||
|
|
||||||
* nginx setup
|
* nginx setup
|
||||||
sudo apt-get install nginx
|
sudo apt-get install nginx
|
||||||
|
|
||||||
|
add this to local_settings.py:
|
||||||
|
XACCELREDIRECT = True
|
||||||
|
|
||||||
|
setup nginx according to etc/nginx/vhost.in
|
||||||
|
|
||||||
* apache2 setup
|
* apache2 setup
|
||||||
sudo apt-get install apache2-mpm-prefork
|
apt-get install apache2-mpm-prefork libapache2-mod-xsendfile
|
||||||
sudo apt-get install libapache2-mod-xsendfile
|
|
||||||
|
|
||||||
|
add this to local_settings.py:
|
||||||
|
XSENDFILE = True
|
||||||
|
|
||||||
|
setup apache according to etc/apache2/vhost.in
|
||||||
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"pk": 1,
|
|
||||||
"model": "backend.reviewwhitelist",
|
|
||||||
"fields": {
|
|
||||||
"url": "://filmcritic.com",
|
|
||||||
"name": "Filmcritic"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pk": 2,
|
|
||||||
"model": "backend.reviewwhitelist",
|
|
||||||
"fields": {
|
|
||||||
"url": "villagevoice.com",
|
|
||||||
"name": "Village Voice"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pk": 3,
|
|
||||||
"model": "backend.reviewwhitelist",
|
|
||||||
"fields": {
|
|
||||||
"url": "salon.com",
|
|
||||||
"name": "Salon.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pk": 4,
|
|
||||||
"model": "backend.reviewwhitelist",
|
|
||||||
"fields": {
|
|
||||||
"url": "rottentomatoes.com",
|
|
||||||
"name": "Rotten Tomatoes"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pk": 5,
|
|
||||||
"model": "backend.reviewwhitelist",
|
|
||||||
"fields": {
|
|
||||||
"url": "nytimes.com",
|
|
||||||
"name": "New York Times"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pk": 6,
|
|
||||||
"model": "backend.reviewwhitelist",
|
|
||||||
"fields": {
|
|
||||||
"url": "metacritic.com",
|
|
||||||
"name": "Metacritic"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pk": 7,
|
|
||||||
"model": "backend.reviewwhitelist",
|
|
||||||
"fields": {
|
|
||||||
"url": "sensesofcinema.com",
|
|
||||||
"name": "Senses of Cinema"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import ox
|
||||||
from archive import extract
|
from archive import extract
|
||||||
from clip.models import Clip
|
from clip.models import Clip
|
||||||
|
|
||||||
|
from item.utils import sort_string
|
||||||
import managers
|
import managers
|
||||||
import utils
|
import utils
|
||||||
from tasks import update_matching_events, update_matching_places
|
from tasks import update_matching_events, update_matching_places
|
||||||
|
@ -34,6 +34,7 @@ class Annotation(models.Model):
|
||||||
|
|
||||||
layer = models.CharField(max_length=255, db_index=True)
|
layer = models.CharField(max_length=255, db_index=True)
|
||||||
value = models.TextField()
|
value = models.TextField()
|
||||||
|
sortvalue = models.CharField(max_length=1000, null=True, blank=True, db_index=True)
|
||||||
|
|
||||||
def editable(self, user):
|
def editable(self, user):
|
||||||
if user.is_authenticated():
|
if user.is_authenticated():
|
||||||
|
@ -57,6 +58,15 @@ class Annotation(models.Model):
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
set_public_id = not self.id or not self.public_id
|
set_public_id = not self.id or not self.public_id
|
||||||
|
if self.value:
|
||||||
|
sortvalue = ox.stripTags(self.value).strip()
|
||||||
|
sortvalue = sort_string(sortvalue)
|
||||||
|
if sortvalue:
|
||||||
|
self.sortvalue = sortvalue[:1000]
|
||||||
|
else:
|
||||||
|
self.sortvalue = None
|
||||||
|
else:
|
||||||
|
self.sortvalue = None
|
||||||
|
|
||||||
#no clip or update clip
|
#no clip or update clip
|
||||||
def get_layer(id):
|
def get_layer(id):
|
||||||
|
|
|
@ -51,6 +51,10 @@ class MetaClip:
|
||||||
for key in j.keys():
|
for key in j.keys():
|
||||||
if key not in keys:
|
if key not in keys:
|
||||||
del j[key]
|
del j[key]
|
||||||
|
#needed here to make item find with clips work
|
||||||
|
if 'annotations' in keys:
|
||||||
|
j['annotations'] = [a.json(keys=['value', 'id', 'layer'])
|
||||||
|
for a in self.annotations.filter(layer__in=self.layers)]
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if key not in clip_keys and key not in j:
|
if key not in clip_keys and key not in j:
|
||||||
value = self.item.get(key)
|
value = self.item.get(key)
|
||||||
|
|
|
@ -35,14 +35,14 @@ def order_query(qs, sort):
|
||||||
if operator != '-':
|
if operator != '-':
|
||||||
operator = ''
|
operator = ''
|
||||||
clip_keys = ('public_id', 'start', 'end', 'hue', 'saturation', 'lightness', 'volume',
|
clip_keys = ('public_id', 'start', 'end', 'hue', 'saturation', 'lightness', 'volume',
|
||||||
'annotations__value', 'videoRatio',
|
'annotations__sortvalue', 'videoRatio',
|
||||||
'director', 'title')
|
'director', 'title')
|
||||||
key = {
|
key = {
|
||||||
'id': 'public_id',
|
'id': 'public_id',
|
||||||
'in': 'start',
|
'in': 'start',
|
||||||
'out': 'end',
|
'out': 'end',
|
||||||
'position': 'start',
|
'position': 'start',
|
||||||
'text': 'annotations__value',
|
'text': 'annotations__sortvalue',
|
||||||
'videoRatio': 'aspect_ratio',
|
'videoRatio': 'aspect_ratio',
|
||||||
}.get(e['key'], e['key'])
|
}.get(e['key'], e['key'])
|
||||||
if key.startswith('clip:'):
|
if key.startswith('clip:'):
|
||||||
|
|
|
@ -21,7 +21,8 @@ def plural_key(term):
|
||||||
|
|
||||||
|
|
||||||
def sort_string(string):
|
def sort_string(string):
|
||||||
string = string.replace(u'Þ', 'Th')
|
string = string.replace(u'Æ', 'AE').replace(u'Ø', 'O').replace(u'Þ', 'Th')
|
||||||
|
|
||||||
#pad numbered titles
|
#pad numbered titles
|
||||||
string = re.sub('(\d+)', lambda x: '%010d' % int(x.group(0)), string)
|
string = re.sub('(\d+)', lambda x: '%010d' % int(x.group(0)), string)
|
||||||
return unicodedata.normalize('NFKD', string)
|
return unicodedata.normalize('NFKD', string)
|
||||||
|
|
|
@ -48,7 +48,6 @@ class Person(models.Model):
|
||||||
if not self.sortname:
|
if not self.sortname:
|
||||||
self.sortname = ox.get_sort_name(self.name)
|
self.sortname = ox.get_sort_name(self.name)
|
||||||
self.sortname = unicodedata.normalize('NFKD', self.sortname)
|
self.sortname = unicodedata.normalize('NFKD', self.sortname)
|
||||||
self.sortname = self.sortname.replace(u'Æ', 'AE').replace(u'Ø', 'O').replace(u'Þ', 'P')
|
|
||||||
self.sortsortname = utils.sort_string(self.sortname)
|
self.sortsortname = utils.sort_string(self.sortname)
|
||||||
self.numberofnames = len(self.name.split(' '))
|
self.numberofnames = len(self.name.split(' '))
|
||||||
super(Person, self).save(*args, **kwargs)
|
super(Person, self).save(*args, **kwargs)
|
||||||
|
|
|
@ -42,7 +42,6 @@ APPEND_SLASH = False
|
||||||
# Example: "/home/media/media.lawrence.com/"
|
# Example: "/home/media/media.lawrence.com/"
|
||||||
MEDIA_ROOT = normpath(join(PROJECT_ROOT, '..', 'data'))
|
MEDIA_ROOT = normpath(join(PROJECT_ROOT, '..', 'data'))
|
||||||
STATIC_ROOT = normpath(join(PROJECT_ROOT, '..', 'static'))
|
STATIC_ROOT = normpath(join(PROJECT_ROOT, '..', 'static'))
|
||||||
TESTS_ROOT = join(PROJECT_ROOT, 'tests')
|
|
||||||
|
|
||||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
# 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).
|
# trailing slash if there is a path component (optional in other cases).
|
||||||
|
@ -183,11 +182,12 @@ VIDEO_PREFIX=''
|
||||||
#VIDEO_PREFIX='videoPART.example.com'
|
#VIDEO_PREFIX='videoPART.example.com'
|
||||||
#SESSION_COOKIE_DOMAIN=*.example.com"
|
#SESSION_COOKIE_DOMAIN=*.example.com"
|
||||||
|
|
||||||
|
SCRIPT_ROOT = normpath(join(PROJECT_ROOT, '..', 'scripts'))
|
||||||
#copy scripts and adjust to customize
|
#copy scripts and adjust to customize
|
||||||
ITEM_POSTER = join('scripts', 'oxdb_poster')
|
ITEM_POSTER = join(SCRIPT_ROOT, 'oxdb_poster')
|
||||||
#ITEM_POSTER = join('scripts', 'padma_poster')
|
#ITEM_POSTER = join(SCRIPT_ROOT, 'padma_poster')
|
||||||
ITEM_ICON = join('scripts', 'item_icon')
|
ITEM_ICON = join(SCRIPT_ROOT, 'item_icon')
|
||||||
LIST_ICON = join('scripts', 'list_icon')
|
LIST_ICON = join(SCRIPT_ROOT, 'list_icon')
|
||||||
|
|
||||||
|
|
||||||
#you can ignore things below this line
|
#you can ignore things below this line
|
||||||
|
|
|
@ -30,14 +30,13 @@ urlpatterns = patterns('',
|
||||||
(r'^robots.txt$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'robots.txt'), 'content_type': 'text/plain'}),
|
(r'^robots.txt$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'robots.txt'), 'content_type': 'text/plain'}),
|
||||||
(r'^favicon.ico$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'png/icon.16.png'), 'content_type': 'image/x-icon'}),
|
(r'^favicon.ico$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'png/icon.16.png'), 'content_type': 'image/x-icon'}),
|
||||||
)
|
)
|
||||||
if settings.DEBUG:
|
#if settings.DEBUG:
|
||||||
|
#sould this not be enabled by default? nginx should handle those
|
||||||
urlpatterns += patterns('',
|
urlpatterns += patterns('',
|
||||||
(r'^data/(?P<path>.*)$', 'django.views.static.serve',
|
(r'^data/(?P<path>.*)$', 'django.views.static.serve',
|
||||||
{'document_root': settings.MEDIA_ROOT}),
|
{'document_root': settings.MEDIA_ROOT}),
|
||||||
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
|
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
|
||||||
{'document_root': settings.STATIC_ROOT}),
|
{'document_root': settings.STATIC_ROOT}),
|
||||||
(r'^tests/(?P<path>.*)$', 'django.views.static.serve',
|
|
||||||
{'document_root': settings.TESTS_ROOT}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns += patterns('',
|
urlpatterns += patterns('',
|
||||||
|
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
@ -4,7 +4,7 @@
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import os
|
import os
|
||||||
|
|
||||||
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
#using virtualenv's activate_this.py to reorder sys.path
|
#using virtualenv's activate_this.py to reorder sys.path
|
||||||
activate_this = os.path.join(root_dir, 'bin', 'activate_this.py')
|
activate_this = os.path.join(root_dir, 'bin', 'activate_this.py')
|
|
@ -4,7 +4,7 @@
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import os
|
import os
|
||||||
|
|
||||||
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
#using virtualenv's activate_this.py to reorder sys.path
|
#using virtualenv's activate_this.py to reorder sys.path
|
||||||
activate_this = os.path.join(root_dir, 'bin', 'activate_this.py')
|
activate_this = os.path.join(root_dir, 'bin', 'activate_this.py')
|
|
@ -4,7 +4,7 @@
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import os
|
import os
|
||||||
|
|
||||||
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
#using virtualenv's activate_this.py to reorder sys.path
|
#using virtualenv's activate_this.py to reorder sys.path
|
||||||
activate_this = os.path.join(root_dir, 'bin', 'activate_this.py')
|
activate_this = os.path.join(root_dir, 'bin', 'activate_this.py')
|
|
@ -4,7 +4,7 @@
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import os
|
import os
|
||||||
|
|
||||||
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
#using virtualenv's activate_this.py to reorder sys.path
|
#using virtualenv's activate_this.py to reorder sys.path
|
||||||
activate_this = os.path.join(root_dir, 'bin', 'activate_this.py')
|
activate_this = os.path.join(root_dir, 'bin', 'activate_this.py')
|