forked from 0x2620/pandora
testing interface, more work on backend
This commit is contained in:
parent
b1d4411dc2
commit
88c5c3d9ac
20 changed files with 1883 additions and 311 deletions
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
3
app/models.py
Normal file
3
app/models.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
23
app/tests.py
Normal file
23
app/tests.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
This file demonstrates two different styles of tests (one doctest and one
|
||||||
|
unittest). These will both pass when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace these with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
class SimpleTest(TestCase):
|
||||||
|
def test_basic_addition(self):
|
||||||
|
"""
|
||||||
|
Tests that 1 + 1 always equals 2.
|
||||||
|
"""
|
||||||
|
self.failUnlessEqual(1 + 1, 2)
|
||||||
|
|
||||||
|
__test__ = {"doctest": """
|
||||||
|
Another way to test that 1 + 1 is equal to 2.
|
||||||
|
|
||||||
|
>>> 1 + 1 == 2
|
||||||
|
True
|
||||||
|
"""}
|
||||||
|
|
10
app/views.py
Normal file
10
app/views.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# -*- 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
|
||||||
|
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
context = RequestContext(request, {})
|
||||||
|
return render_to_response('index.html', context)
|
||||||
|
|
18
backend/decorators.py
Normal file
18
backend/decorators.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
|
|
||||||
|
|
||||||
|
def login_required_json(function=None):
|
||||||
|
"""
|
||||||
|
Decorator for views that checks that the user is logged in
|
||||||
|
return json error if not logged in.
|
||||||
|
"""
|
||||||
|
actual_decorator = user_passes_test(
|
||||||
|
lambda u: u.is_authenticated(),
|
||||||
|
login_url='/json/login',
|
||||||
|
)
|
||||||
|
if function:
|
||||||
|
return actual_decorator(function)
|
||||||
|
return actual_decorator
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vi:si:et:sw=4:sts=4:ts=4
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
import random
|
||||||
import os.path
|
import os.path
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -26,8 +27,15 @@ def loadIMDb(imdbId):
|
||||||
try:
|
try:
|
||||||
movie = models.Movie.byImdbId(imdbId)
|
movie = models.Movie.byImdbId(imdbId)
|
||||||
except models.Movie.DoesNotExist:
|
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 = models.Movie()
|
||||||
movie.imdbId = imdbId
|
movie.imdb = imdb
|
||||||
|
|
||||||
info = oxweb.imdb.getMovieInfo(imdbId)
|
info = oxweb.imdb.getMovieInfo(imdbId)
|
||||||
for key in ('title',
|
for key in ('title',
|
||||||
|
@ -40,7 +48,7 @@ def loadIMDb(imdbId):
|
||||||
'season',
|
'season',
|
||||||
'episode'):
|
'episode'):
|
||||||
if key in info:
|
if key in info:
|
||||||
setattr(movie, key, info[key])
|
setattr(movie.imdb, key, info[key])
|
||||||
debug(key, info[key])
|
debug(key, info[key])
|
||||||
_info_map = {
|
_info_map = {
|
||||||
'episode title': 'episode_title',
|
'episode title': 'episode_title',
|
||||||
|
@ -48,18 +56,20 @@ def loadIMDb(imdbId):
|
||||||
}
|
}
|
||||||
for key in _info_map.keys():
|
for key in _info_map.keys():
|
||||||
if key in info:
|
if key in info:
|
||||||
setattr(movie, _info_map.get(key, key), info[key])
|
setattr(movie.imdb, _info_map.get(key, key), info[key])
|
||||||
|
|
||||||
movie.plot = oxweb.imdb.getMoviePlot(imdbId)
|
movie.imdb.plot = oxweb.imdb.getMoviePlot(imdbId)
|
||||||
debug("plot", movie.plot)
|
debug("plot", movie.imdb.plot)
|
||||||
|
|
||||||
movie.runtime = oxweb.imdb.getMovieRuntimeSeconds(imdbId)
|
movie.imdb.runtime = oxweb.imdb.getMovieRuntimeSeconds(imdbId)
|
||||||
business = oxweb.imdb.getMovieBusinessSum(imdbId)
|
business = oxweb.imdb.getMovieBusinessSum(imdbId)
|
||||||
for key in ('gross', 'profit', 'budget'):
|
for key in ('gross', 'profit', 'budget'):
|
||||||
setattr(movie, key, business[key])
|
setattr(movie.imdb, key, business[key])
|
||||||
|
|
||||||
|
movie.imdb.save()
|
||||||
|
movie.oxdbId = "__init__%s" % random.randint(0, 100000)
|
||||||
movie.save()
|
movie.save()
|
||||||
models.AlternativeTitle.objects.filter(movie=movie).delete()
|
models.AlternativeTitle.objects.filter(movie=movie, manual=False).delete()
|
||||||
for i in oxweb.imdb.getMovieAKATitles(imdbId):
|
for i in oxweb.imdb.getMovieAKATitles(imdbId):
|
||||||
t = models.AlternativeTitle()
|
t = models.AlternativeTitle()
|
||||||
t.movie = movie
|
t.movie = movie
|
||||||
|
@ -69,7 +79,7 @@ def loadIMDb(imdbId):
|
||||||
|
|
||||||
#FIXME: related tables should be cleaned to not accumulate cruft
|
#FIXME: related tables should be cleaned to not accumulate cruft
|
||||||
#Country
|
#Country
|
||||||
models.MovieCountry.objects.filter(movie=movie).delete()
|
models.MovieCountry.objects.filter(movie=movie, manual=False).delete()
|
||||||
position = 0
|
position = 0
|
||||||
if 'country' in info:
|
if 'country' in info:
|
||||||
for i in info['country']:
|
for i in info['country']:
|
||||||
|
@ -79,7 +89,7 @@ def loadIMDb(imdbId):
|
||||||
position += 1
|
position += 1
|
||||||
|
|
||||||
#Language
|
#Language
|
||||||
models.MovieLanguage.objects.filter(movie=movie).delete()
|
models.MovieLanguage.objects.filter(movie=movie, manual=False).delete()
|
||||||
position = 0
|
position = 0
|
||||||
if 'language' in info:
|
if 'language' in info:
|
||||||
for i in info['language']:
|
for i in info['language']:
|
||||||
|
@ -89,7 +99,7 @@ def loadIMDb(imdbId):
|
||||||
position += 1
|
position += 1
|
||||||
|
|
||||||
#Location
|
#Location
|
||||||
movie.locations.all().delete()
|
movie.locations_all.filter(manual=False).delete()
|
||||||
locations = oxweb.imdb.getMovieLocations(imdbId)
|
locations = oxweb.imdb.getMovieLocations(imdbId)
|
||||||
for i in locations:
|
for i in locations:
|
||||||
debug("add location", i)
|
debug("add location", i)
|
||||||
|
@ -97,7 +107,7 @@ def loadIMDb(imdbId):
|
||||||
location.movies.add(movie)
|
location.movies.add(movie)
|
||||||
|
|
||||||
#Genre
|
#Genre
|
||||||
movie.genres.all().delete()
|
movie.genres_all.filter(manual=False).delete()
|
||||||
if 'genre' in info:
|
if 'genre' in info:
|
||||||
for i in info['genre']:
|
for i in info['genre']:
|
||||||
debug("add genre", i)
|
debug("add genre", i)
|
||||||
|
@ -105,14 +115,14 @@ def loadIMDb(imdbId):
|
||||||
genre.movies.add(movie)
|
genre.movies.add(movie)
|
||||||
|
|
||||||
#Keyword
|
#Keyword
|
||||||
movie.keywords.all().delete()
|
movie.keywords_all.filter(manual=False).delete()
|
||||||
keywords = oxweb.imdb.getMovieKeywords(imdbId)
|
keywords = oxweb.imdb.getMovieKeywords(imdbId)
|
||||||
for g in keywords:
|
for g in keywords:
|
||||||
debug("add keyword", g)
|
debug("add keyword", g)
|
||||||
keyword = models.Keyword.get_or_create(g)
|
keyword = models.Keyword.get_or_create(g)
|
||||||
keyword.movies.add(movie)
|
keyword.movies.add(movie)
|
||||||
|
|
||||||
movie.trivia.all().delete()
|
movie.trivia_all.filter(manual=False).delete()
|
||||||
position = 0
|
position = 0
|
||||||
trivia = oxweb.imdb.getMovieTrivia(imdbId)
|
trivia = oxweb.imdb.getMovieTrivia(imdbId)
|
||||||
for i in trivia:
|
for i in trivia:
|
||||||
|
@ -125,6 +135,7 @@ def loadIMDb(imdbId):
|
||||||
position += 1
|
position += 1
|
||||||
|
|
||||||
position = 0
|
position = 0
|
||||||
|
models.Cast.objects.filter(movie=movie).filter(manual=False).delete()
|
||||||
credits = oxweb.imdb.getMovieCredits(imdbId)
|
credits = oxweb.imdb.getMovieCredits(imdbId)
|
||||||
for role in credits:
|
for role in credits:
|
||||||
for p in credits[role]:
|
for p in credits[role]:
|
||||||
|
@ -137,18 +148,19 @@ def loadIMDb(imdbId):
|
||||||
models.Cast.link(movie, person, role, character, position)
|
models.Cast.link(movie, person, role, character, position)
|
||||||
position += 1
|
position += 1
|
||||||
|
|
||||||
movie.connections.all().delete()
|
movie.connections_all.filter(manual=False).delete()
|
||||||
connections = oxweb.imdb.getMovieConnections(imdbId)
|
connections = oxweb.imdb.getMovieConnections(imdbId)
|
||||||
for relation in connections:
|
for relation in connections:
|
||||||
for otherId in connections[relation]:
|
for otherId in connections[relation]:
|
||||||
try:
|
try:
|
||||||
object = models.Movie.objects.get(imdbId=otherId)
|
object = models.Movie.objects.get(imdb__imdbId=otherId)
|
||||||
debug("add connection", relation, object)
|
debug("add connection", relation, object)
|
||||||
models.Connection.get_or_create(movie, relation, object)
|
models.Connection.get_or_create(movie, relation, object)
|
||||||
except models.Movie.DoesNotExist:
|
except models.Movie.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
reviews = oxweb.imdb.getMovieExternalReviews(imdbId)
|
reviews = oxweb.imdb.getMovieExternalReviews(imdbId)
|
||||||
|
movie.reviews_all.filter(manual=False).delete()
|
||||||
for r in reviews:
|
for r in reviews:
|
||||||
debug("add review", r)
|
debug("add review", r)
|
||||||
review = models.Review.get_or_create(movie, r)
|
review = models.Review.get_or_create(movie, r)
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.db.models import Q, Manager
|
||||||
|
|
||||||
import models
|
import models
|
||||||
|
|
||||||
|
|
||||||
def keyType(key):
|
def keyType(key):
|
||||||
if key in ('released'):
|
if key in ('released'):
|
||||||
return "date"
|
return "date"
|
||||||
|
@ -29,6 +30,7 @@ class MovieManager(Manager):
|
||||||
also checks for lists.
|
also checks for lists.
|
||||||
range and order must be applied later
|
range and order must be applied later
|
||||||
'''
|
'''
|
||||||
|
q = ''
|
||||||
for i in request.META['QUERY_STRING'].split('&'):
|
for i in request.META['QUERY_STRING'].split('&'):
|
||||||
if i.startswith('q='):
|
if i.startswith('q='):
|
||||||
q = i[2:]
|
q = i[2:]
|
||||||
|
@ -96,6 +98,8 @@ class MovieManager(Manager):
|
||||||
|
|
||||||
#join query with operator
|
#join query with operator
|
||||||
qs = self.get_query_set()
|
qs = self.get_query_set()
|
||||||
|
#only include movies that have hard metadata
|
||||||
|
qs = qs.filter(available=True)
|
||||||
if conditions:
|
if conditions:
|
||||||
q = conditions[0]
|
q = conditions[0]
|
||||||
for c in conditions[1:]:
|
for c in conditions[1:]:
|
||||||
|
@ -122,15 +126,27 @@ class MovieManager(Manager):
|
||||||
qs = qs.filter(listitem__list__id=lqs[0].id)
|
qs = qs.filter(listitem__list__id=lqs[0].id)
|
||||||
return qs
|
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(is_video=True, movie=movie)
|
||||||
|
|
||||||
class ArchiveFileManager(Manager):
|
class ArchiveFileManager(Manager):
|
||||||
def get_query_set(self):
|
def get_query_set(self):
|
||||||
return super(UserFileManager, self).get_query_set()
|
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):
|
def by_oshash(self, oshash):
|
||||||
q = self.get_query_set()
|
q = self.get_query_set()
|
||||||
q.filter(movie_file__oshash=oshash)
|
q.filter(movie_file__oshash=oshash)
|
||||||
if q.count() == 0:
|
if q.count() == 0:
|
||||||
raise models.UserFile.DoesNotExist("%s matching oshash %s does not exist." %
|
raise models.ArchiveFile.DoesNotExist("%s matching oshash %s does not exist." %
|
||||||
(models.UserFile._meta.object_name, oshash))
|
(models.ArchiveFile._meta.object_name, oshash))
|
||||||
return q[0]
|
return q[0]
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
# vi:si:et:sw=4:sts=4:ts=4
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
import re
|
import re
|
||||||
import os.path
|
import os.path
|
||||||
|
import random
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
import oxlib
|
import oxlib
|
||||||
from oxlib import stripTags
|
from oxlib import stripTags
|
||||||
from oxlib.normalize import canonicalTitle, canonicalName
|
from oxlib.normalize import canonicalTitle, canonicalName
|
||||||
|
@ -14,75 +15,157 @@ import utils
|
||||||
import managers
|
import managers
|
||||||
|
|
||||||
|
|
||||||
class Movie(models.Model):
|
class MovieImdb(models.Model):
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
modified = models.DateTimeField(auto_now=True)
|
modified = models.DateTimeField(auto_now=True)
|
||||||
accessed = models.DateTimeField(null=True, blank=True)
|
|
||||||
|
|
||||||
movieId = models.CharField(max_length=128, unique=True, blank=True)
|
imdbId = models.CharField(max_length=7, unique=True)
|
||||||
imdbId = models.CharField(max_length=7, unique=True, blank=True)
|
|
||||||
oxdbId = models.CharField(max_length=42, unique=True, blank=True)
|
|
||||||
title = models.CharField(max_length=1000)
|
title = models.CharField(max_length=1000)
|
||||||
year = models.CharField(max_length=4)
|
year = models.CharField(max_length=4)
|
||||||
runtime = models.IntegerField(null=True, blank=True)
|
runtime = models.IntegerField(null=True, blank=True)
|
||||||
release_date = models.DateField(null=True, blank=True)
|
release_date = models.DateField(null=True, blank=True)
|
||||||
|
|
||||||
tagline = models.TextField(blank=True)
|
tagline = models.TextField(blank=True)
|
||||||
plot = models.TextField(blank=True)
|
plot = models.TextField(blank=True)
|
||||||
plot_outline = models.TextField(blank=True)
|
plot_outline = models.TextField(blank=True)
|
||||||
|
|
||||||
rating = models.IntegerField(null=True, blank=True)
|
rating = models.FloatField(null=True, blank=True)
|
||||||
votes = models.IntegerField(null=True, blank=True)
|
votes = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
budget = models.IntegerField(null=True, blank=True)
|
budget = models.IntegerField(null=True, blank=True)
|
||||||
gross = models.IntegerField(null=True, blank=True)
|
gross = models.IntegerField(null=True, blank=True)
|
||||||
profit = models.IntegerField(null=True, blank=True)
|
profit = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
#FIXME: how to deal with files now?
|
series_imdb = models.CharField(max_length=7, default='')
|
||||||
#files =
|
series_title = models.TextField(blank=True, default='')
|
||||||
files_modified = models.DateTimeField(auto_now=True)
|
episode_title = models.TextField(blank=True, default='')
|
||||||
filename = models.TextField(blank=True)
|
season = models.IntegerField(default=-1)
|
||||||
extracted = models.IntegerField(null=True, blank=True)
|
episode = models.IntegerField(default=-1)
|
||||||
|
|
||||||
#length = models.IntegerField(null=True, blank=True)
|
class MovieOxdb(models.Model):
|
||||||
duration = models.FloatField(null=True, blank=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
title = models.CharField(max_length=1000)
|
||||||
|
year = models.CharField(max_length=4)
|
||||||
|
runtime = models.IntegerField(null=True, blank=True)
|
||||||
|
release_date = models.DateField(null=True, blank=True)
|
||||||
|
tagline = models.TextField(blank=True)
|
||||||
|
plot = models.TextField(blank=True)
|
||||||
|
plot_outline = models.TextField(blank=True)
|
||||||
|
|
||||||
|
rating = models.FloatField(null=True, blank=True)
|
||||||
|
votes = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
budget = models.IntegerField(null=True, blank=True)
|
||||||
|
gross = models.IntegerField(null=True, blank=True)
|
||||||
|
profit = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
series_imdb = models.CharField(max_length=7, default='')
|
||||||
|
series_title = models.TextField(blank=True, default='')
|
||||||
|
episode_title = models.TextField(blank=True, default='')
|
||||||
|
season = models.IntegerField(default=-1)
|
||||||
|
episode = models.IntegerField(default=-1)
|
||||||
|
|
||||||
|
def newMovie(title, director, year):
|
||||||
|
movie = Movie()
|
||||||
|
oxdb = MovieOxdb()
|
||||||
|
oxdb.save()
|
||||||
|
movie.oxdb = oxdb
|
||||||
|
movie.oxdb.title = title
|
||||||
|
movie.oxdb.year = str(year)
|
||||||
|
movie.oxdb.save()
|
||||||
|
movie.oxdbId = "__init__%s" % random.randint(0, 100000)
|
||||||
|
movie.save()
|
||||||
|
movie.oxdbId = movie.oxid()
|
||||||
|
movie.save()
|
||||||
|
return movie
|
||||||
|
|
||||||
|
class Movie(models.Model):
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
#only movies that have metadata from files are available,
|
||||||
|
#this is indicated by setting available to True
|
||||||
|
available = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
movieId = models.CharField(max_length=128, unique=True, blank=True)
|
||||||
|
oxdbId = models.CharField(max_length=42, unique=True, blank=True)
|
||||||
|
|
||||||
|
imdb = models.OneToOneField('MovieImdb', null=True, related_name='movie')
|
||||||
|
oxdb = models.OneToOneField('MovieOxdb', null=True, related_name='movie')
|
||||||
|
|
||||||
objects = managers.MovieManager()
|
objects = managers.MovieManager()
|
||||||
|
|
||||||
#FIXME: should this be a done via a manager for person?
|
def get(self, key, default=None):
|
||||||
def directors(self):
|
if self.oxdb and getattr(self.oxdb, key):
|
||||||
return self.people.filter(cast__role='directors').order_by('cast__position')
|
return getattr(self.oxdb, key)
|
||||||
def writers(self):
|
if self.imdb:
|
||||||
return self.people.filter(cast__role='writers').order_by('cast__position')
|
return getattr(self.imdb, key)
|
||||||
def editors(self):
|
return default
|
||||||
return self.people.filter(cast__role='editors').order_by('cast__position')
|
|
||||||
def producers(self):
|
def _manual(self, qs, f='manual'):
|
||||||
return self.people.filter(cast__role='producers').order_by('cast__position')
|
if qs.filter(**{f:True}).count() > 0:
|
||||||
def cinematographers(self):
|
return qs.exclude(**{f:False})
|
||||||
return self.people.filter(cast__role='cinematographers').order_by('cast__position')
|
return qs.exclude(**{f:True})
|
||||||
|
|
||||||
|
def directors(self):
|
||||||
|
qs = self.people.filter(cast__role='directors').order_by('cast__position')
|
||||||
|
return self._manual(qs, 'cast__manual')
|
||||||
|
def writers(self):
|
||||||
|
qs = self.people.filter(cast__role='writers').order_by('cast__position')
|
||||||
|
return self._manual(qs, 'cast__manual')
|
||||||
|
def editors(self):
|
||||||
|
qs = self.people.filter(cast__role='editors').order_by('cast__position')
|
||||||
|
return self._manual(qs, 'cast__manual')
|
||||||
|
def producers(self):
|
||||||
|
qs = self.people.filter(cast__role='producers').order_by('cast__position')
|
||||||
|
return self._manual(qs, 'cast__manual')
|
||||||
|
def cinematographers(self):
|
||||||
|
qs = self.people.filter(cast__role='cinematographers').order_by('cast__position')
|
||||||
|
return self._manual(qs, 'cast__manual')
|
||||||
|
|
||||||
#FIXME: include role and character
|
|
||||||
def cast(self):
|
def cast(self):
|
||||||
cast = []
|
cast = []
|
||||||
for c in Cast.objects.filter(movie=self, role='cast').order_by('position'):
|
qs = Cast.objects.filter(movie=self, role='cast').order_by('position')
|
||||||
|
qs = self._manual(qs)
|
||||||
|
for c in qs:
|
||||||
cast.append((c.person.name, c.character))
|
cast.append((c.person.name, c.character))
|
||||||
return tuple(cast)
|
return tuple(cast)
|
||||||
#return self.person.filter(cast__role='cast').order_by('cast__position')
|
|
||||||
|
def alternative_titles(self):
|
||||||
|
return self._manual(self.alternative_titles_all)
|
||||||
|
def genres(self):
|
||||||
|
return self._manual(self.genres_all)
|
||||||
|
def keywords(self):
|
||||||
|
return self._manual(self.keywords_all)
|
||||||
|
def countries(self):
|
||||||
|
return self._manual(self.countries_all, 'moviecountry__manual')
|
||||||
|
def languages(self):
|
||||||
|
return self._manual(self.languages_all, 'movielanguage__manual')
|
||||||
|
def trivia(self):
|
||||||
|
return self._manual(self.trivia_all)
|
||||||
|
def locations(self):
|
||||||
|
return self._manual(self.locations_all)
|
||||||
|
def connections(self):
|
||||||
|
return self._manual(self.connections_all)
|
||||||
|
|
||||||
def connections_json(self):
|
def connections_json(self):
|
||||||
connections = {}
|
connections = {}
|
||||||
for connection in self.connections.all():
|
for connection in self.connections():
|
||||||
if connection.relation not in connections:
|
if connection.relation not in connections:
|
||||||
connections[connection.relation] = []
|
connections[connection.relation] = []
|
||||||
connections[connection.relation].append(connection.object.movieId)
|
connections[connection.relation].append(connection.object.movieId)
|
||||||
return connections
|
return connections
|
||||||
|
|
||||||
def filtered_reviews(self):
|
def reviews(self):
|
||||||
|
q = self.reviews_all.filter(manual=True)
|
||||||
|
if q.count() > 0:
|
||||||
|
return q
|
||||||
whitelist = ReviewWhitelist.objects.all()
|
whitelist = ReviewWhitelist.objects.all()
|
||||||
q = Q(id=-1)
|
q = Q(id=-1)
|
||||||
for w in whitelist:
|
for w in whitelist:
|
||||||
q = q | Q(url__contains=w.url)
|
q = q | Q(url__contains=w.url)
|
||||||
return self.reviews.filter(q)
|
return self.reviews_all.filter(q).filter(manual=False)
|
||||||
|
|
||||||
rights_level = models.IntegerField(default=-1)
|
rights_level = models.IntegerField(default=-1)
|
||||||
|
|
||||||
|
@ -92,13 +175,6 @@ class Movie(models.Model):
|
||||||
open_subtitle_id = models.IntegerField(null=True, blank=True)
|
open_subtitle_id = models.IntegerField(null=True, blank=True)
|
||||||
wikipedia_url = models.TextField(blank=True)
|
wikipedia_url = models.TextField(blank=True)
|
||||||
|
|
||||||
#Series information
|
|
||||||
series_imdb = models.CharField(max_length=7, default='')
|
|
||||||
series_title = models.TextField(blank=True, default='')
|
|
||||||
episode_title = models.TextField(blank=True, default='')
|
|
||||||
season = models.IntegerField(default=-1)
|
|
||||||
episode = models.IntegerField(default=-1)
|
|
||||||
|
|
||||||
#what of this is still required?
|
#what of this is still required?
|
||||||
still_pos = models.IntegerField(null=True, blank=True)
|
still_pos = models.IntegerField(null=True, blank=True)
|
||||||
poster = models.TextField(blank=True)
|
poster = models.TextField(blank=True)
|
||||||
|
@ -110,11 +186,16 @@ class Movie(models.Model):
|
||||||
scene_height = models.IntegerField(null=True, blank=True)
|
scene_height = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'%s (%s)' % (self.title, self.year)
|
return u'%s (%s)' % (self.get('title'), self.get('year'))
|
||||||
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.imdbId:
|
if not self.oxdb:
|
||||||
mid = self.imdbId
|
oxdb = MovieOxdb()
|
||||||
|
oxdb.save()
|
||||||
|
self.oxdb = oxdb
|
||||||
|
if self.imdb:
|
||||||
|
mid = self.imdb.imdbId
|
||||||
else:
|
else:
|
||||||
mid = self.oxdbId
|
mid = self.oxdbId
|
||||||
self.movieId = mid
|
self.movieId = mid
|
||||||
|
@ -133,33 +214,41 @@ class Movie(models.Model):
|
||||||
|
|
||||||
'countries': 'country',
|
'countries': 'country',
|
||||||
'directors': 'director',
|
'directors': 'director',
|
||||||
'genres': 'genres',
|
'languages': 'language',
|
||||||
'keywords': 'keywords',
|
'genres': 'genre',
|
||||||
|
'keywords': 'keyword',
|
||||||
'cast': 'cast',
|
'cast': 'cast',
|
||||||
'series_title': 'series_title',
|
'series_title': 'series_title',
|
||||||
'episode_title': 'episode_title',
|
'episode_title': 'episode_title',
|
||||||
'season': 'season',
|
'season': 'season',
|
||||||
'episode': 'episode',
|
'episode': 'episode',
|
||||||
'filtered_reviews': 'reviews',
|
'reviews': 'reviews',
|
||||||
'trivia': 'trivia',
|
'trivia': 'trivia',
|
||||||
|
'rating': 'rating',
|
||||||
|
'votes': 'votes',
|
||||||
'alternative_titles': 'alternative_titles',
|
'alternative_titles': 'alternative_titles',
|
||||||
'connections': 'connections_json'
|
'connections_json': 'connections'
|
||||||
}
|
}
|
||||||
def json(self, fields=None):
|
def json(self, fields=None):
|
||||||
movie = {}
|
movie = {}
|
||||||
for key in self._public_fields:
|
for key in self._public_fields:
|
||||||
pub_key = self._public_fields.get(key, key)
|
pub_key = self._public_fields.get(key, key)
|
||||||
if not fields or pub_key in fields:
|
if not fields or pub_key in fields:
|
||||||
value = getattr(self, key)
|
if hasattr(self, key):
|
||||||
if key in ('directors', 'writers', 'filtered_reviews'):
|
value = getattr(self, key)
|
||||||
|
else:
|
||||||
|
value = self.get(key)
|
||||||
|
if key in ('directors', 'writers', 'reviews',
|
||||||
|
'countries', 'languages', 'keywords', 'genres', 'trivia', 'alternative_titles'):
|
||||||
movie[pub_key] = tuple([v.json() for v in value()])
|
movie[pub_key] = tuple([v.json() for v in value()])
|
||||||
elif key in ('countries', 'keywords', 'genres', 'trivia', 'alternative_titles'):
|
elif callable(value):
|
||||||
movie[pub_key] = tuple([v.json() for v in value.all()])
|
movie[pub_key] = value()
|
||||||
else:
|
else:
|
||||||
movie[pub_key] = value
|
movie[pub_key] = value
|
||||||
for f in fields:
|
if fields:
|
||||||
if f.endswith('.length') and f[:-7] in ('cast', 'genre', 'trivia'):
|
for f in fields:
|
||||||
movie[f] = getattr(self.sort.all()[0], f[:-7])
|
if f.endswith('.length') and f[:-7] in ('cast', 'genre', 'trivia'):
|
||||||
|
movie[f] = getattr(self.sort.all()[0], f[:-7])
|
||||||
return movie
|
return movie
|
||||||
|
|
||||||
def fields(self):
|
def fields(self):
|
||||||
|
@ -181,7 +270,7 @@ class Movie(models.Model):
|
||||||
byMovieId = classmethod(byMovieId)
|
byMovieId = classmethod(byMovieId)
|
||||||
|
|
||||||
def byImdbId(self, imdbId):
|
def byImdbId(self, imdbId):
|
||||||
return self.objects.get(imdbId=imdbId)
|
return self.objects.get(imdb__imdbId=imdbId)
|
||||||
byImdbId = classmethod(byImdbId)
|
byImdbId = classmethod(byImdbId)
|
||||||
|
|
||||||
def byOxdbId(self, oxdbId):
|
def byOxdbId(self, oxdbId):
|
||||||
|
@ -190,43 +279,44 @@ class Movie(models.Model):
|
||||||
|
|
||||||
def oxid(self):
|
def oxid(self):
|
||||||
directors = ','.join([d.name for d in self.directors()])
|
directors = ','.join([d.name for d in self.directors()])
|
||||||
return utils.oxid(self.title, directors, self.year,
|
return utils.oxid(self.get('title', ''), directors, self.get('year', ''),
|
||||||
self.series_title, self.episode_title, self.season, self.episode)
|
self.get('series_title', ''), self.get('episode_title', ''),
|
||||||
|
self.get('season', ''), self.get('episode', ''))
|
||||||
|
|
||||||
def updateFind(self):
|
def updateFind(self):
|
||||||
if self.find.count() == 0:
|
try:
|
||||||
f = MovieFind()
|
f = self.find
|
||||||
f.movie = self
|
except MovieFind.DoesNotExist:
|
||||||
else:
|
f = MovieFind(movie=self)
|
||||||
f = self.find.all()[0]
|
|
||||||
f.title = self.title + ' '.join([t.title for t in self.alternative_titles.all()])
|
f.title = self.get('title') + ' '.join([t.title for t in self.alternative_titles()])
|
||||||
f.director = ' '.join([i.name for i in self.directors()])
|
f.director = ' '.join([i.name for i in self.directors()])
|
||||||
f.country = ' '.join([i.name for i in self.countries.all()])
|
f.country = ' '.join([i.name for i in self.countries()])
|
||||||
f.year = self.year
|
f.year = self.get('year', '')
|
||||||
f.language = ' '.join([i.name for i in self.languages.all()])
|
f.language = ' '.join([i.name for i in self.languages()])
|
||||||
f.writer = ' '.join([i.name for i in self.writers()])
|
f.writer = ' '.join([i.name for i in self.writers()])
|
||||||
f.producer = ' '.join([i.name for i in self.producers()])
|
f.producer = ' '.join([i.name for i in self.producers()])
|
||||||
f.editor = ' '.join([i.name for i in self.editors()])
|
f.editor = ' '.join([i.name for i in self.editors()])
|
||||||
f.cinematographer = ' '.join([i.name for i in self.cinematographers()])
|
f.cinematographer = ' '.join([i.name for i in self.cinematographers()])
|
||||||
f.cast = ' '.join(['%s %s' % i for i in self.cast()])
|
f.cast = ' '.join(['%s %s' % i for i in self.cast()])
|
||||||
f.genre = ' '.join([i.name for i in self.genres.all()])
|
f.genre = ' '.join([i.name for i in self.genres()])
|
||||||
f.keywords = ' '.join([i.name for i in self.keywords.all()])
|
f.keywords = ' '.join([i.name for i in self.keywords()])
|
||||||
f.summary = self.plot + self.plot_outline
|
f.summary = self.get('plot', '') + self.get('plot_outline', '')
|
||||||
f.trivia = ' '.join([i.trivia for i in self.trivia.all()])
|
f.trivia = ' '.join([i.trivia for i in self.trivia()])
|
||||||
f.location = ' '.join([i.name for i in self.locations.all()])
|
f.location = ' '.join([i.name for i in self.locations()])
|
||||||
f.filename = self.filename
|
#FIXME: collate filenames
|
||||||
f.all = ' '.join([f.title, f.director, f.country, f.year, f.language,
|
#f.filename = self.filename
|
||||||
|
f.all = ' '.join(filter(None, [f.title, f.director, f.country, f.year, f.language,
|
||||||
f.writer, f.producer, f.editor, f.cinematographer,
|
f.writer, f.producer, f.editor, f.cinematographer,
|
||||||
f.cast, f.genre, f.keywords, f.summary, f.trivia,
|
f.cast, f.genre, f.keywords, f.summary, f.trivia,
|
||||||
f.location, f.filename])
|
f.location, f.filename]))
|
||||||
f.save()
|
f.save()
|
||||||
|
|
||||||
def updateSort(self):
|
def updateSort(self):
|
||||||
if self.sort.count() == 0:
|
try:
|
||||||
s = MovieSort()
|
s = self.sort
|
||||||
s.movie = self
|
except MovieSort.DoesNotExist:
|
||||||
else:
|
s = MovieSort(movie=self)
|
||||||
s = self.sort.all()[0]
|
|
||||||
|
|
||||||
def sortName(value):
|
def sortName(value):
|
||||||
sort_value = '~'
|
sort_value = '~'
|
||||||
|
@ -239,7 +329,7 @@ class Movie(models.Model):
|
||||||
return sort_value
|
return sort_value
|
||||||
|
|
||||||
#title
|
#title
|
||||||
title = canonicalTitle(self.title)
|
title = canonicalTitle(self.get('title'))
|
||||||
title = re.sub(u'[\'!¿¡,\.;\-"\:\*\[\]]', '', title)
|
title = re.sub(u'[\'!¿¡,\.;\-"\:\*\[\]]', '', title)
|
||||||
title = title.replace(u'Æ', 'Ae')
|
title = title.replace(u'Æ', 'Ae')
|
||||||
#pad numbered titles
|
#pad numbered titles
|
||||||
|
@ -253,8 +343,8 @@ class Movie(models.Model):
|
||||||
directors = ','.join([i.name for i in self.directors()])
|
directors = ','.join([i.name for i in self.directors()])
|
||||||
s.director = sortName(directors)
|
s.director = sortName(directors)
|
||||||
|
|
||||||
s.country = ','.join([i.name for i in self.countries.all()])
|
s.country = ','.join([i.name for i in self.countries()])
|
||||||
s.year = self.year
|
s.year = self.get('year', '')
|
||||||
|
|
||||||
names = ','.join([i.name for i in self.producers()])
|
names = ','.join([i.name for i in self.producers()])
|
||||||
s.producer = sortName(names)
|
s.producer = sortName(names)
|
||||||
|
@ -265,16 +355,19 @@ class Movie(models.Model):
|
||||||
names = ','.join([i.name for i in self.cinematographers()])
|
names = ','.join([i.name for i in self.cinematographers()])
|
||||||
s.cinematographer = sortName(names)
|
s.cinematographer = sortName(names)
|
||||||
|
|
||||||
s.country = ','.join([i.name for i in self.languages.all()])
|
s.language = ','.join([i.name for i in self.languages()])
|
||||||
s.runtime = self.runtime
|
s.country = ','.join([i.name for i in self.countries()])
|
||||||
|
s.runtime = self.get('runtime', 0)
|
||||||
|
|
||||||
s.keywords = self.keywords.all().count()
|
s.keywords = self.keywords().count()
|
||||||
s.genre = self.genres.all().count()
|
s.genre = self.genres().count()
|
||||||
s.cast = len(self.cast())
|
s.cast = len(self.cast())
|
||||||
s.summary = len(self.plot.split())
|
s.summary = len(self.get('plot', '').split())
|
||||||
s.trivia = self.trivia.all().count()
|
s.trivia = self.trivia().count()
|
||||||
s.connections = self.connections.all().count()
|
s.connections = self.connections().count()
|
||||||
s.movieId = self.movieId
|
s.movieId = self.movieId
|
||||||
|
s.rating = self.get('rating', -1)
|
||||||
|
s.votes = self.get('votes', -1)
|
||||||
|
|
||||||
# data from related subtitles
|
# data from related subtitles
|
||||||
s.scenes = 0 #FIXME
|
s.scenes = 0 #FIXME
|
||||||
|
@ -296,29 +389,29 @@ class MovieFind(models.Model):
|
||||||
"""
|
"""
|
||||||
used to search movies, all search values are in here
|
used to search movies, all search values are in here
|
||||||
"""
|
"""
|
||||||
movie = models.ForeignKey('Movie', related_name='find', unique=True)
|
movie = models.OneToOneField('Movie', related_name='find', primary_key=True)
|
||||||
|
|
||||||
all = models.TextField(blank=True)
|
all = models.TextField(blank=True)
|
||||||
title = models.CharField(max_length=1000)
|
title = models.CharField(max_length=1000)
|
||||||
director = models.TextField(blank=True)
|
director = models.TextField(blank=True, default='')
|
||||||
country = models.TextField(blank=True)
|
country = models.TextField(blank=True, default='')
|
||||||
year = models.CharField(max_length=4)
|
year = models.CharField(max_length=4)
|
||||||
language = models.TextField(blank=True)
|
language = models.TextField(blank=True, default='')
|
||||||
writer = models.TextField(blank=True)
|
writer = models.TextField(blank=True, default='')
|
||||||
producer = models.TextField(blank=True)
|
producer = models.TextField(blank=True, default='')
|
||||||
editor = models.TextField(blank=True)
|
editor = models.TextField(blank=True, default='')
|
||||||
cinematographer = models.TextField(blank=True)
|
cinematographer = models.TextField(blank=True, default='')
|
||||||
cast = models.TextField(blank=True)
|
cast = models.TextField(blank=True, default='')
|
||||||
#person
|
#person
|
||||||
|
|
||||||
genre = models.TextField(blank=True)
|
genre = models.TextField(blank=True)
|
||||||
keywords = models.TextField(blank=True)
|
keywords = models.TextField(blank=True)
|
||||||
summary = models.TextField(blank=True)
|
summary = models.TextField(blank=True)
|
||||||
trivia = models.TextField(blank=True)
|
trivia = models.TextField(blank=True)
|
||||||
locations = models.TextField(blank=True)
|
locations = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
#only for own files or as admin?
|
#only for own files or as admin?
|
||||||
filename = models.TextField(blank=True)
|
filename = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
_private_fields = ('id', 'movie')
|
_private_fields = ('id', 'movie')
|
||||||
_public_names = {
|
_public_names = {
|
||||||
|
@ -338,7 +431,7 @@ class MovieSort(models.Model):
|
||||||
"""
|
"""
|
||||||
used to sort movies, all sort values are in here
|
used to sort movies, all sort values are in here
|
||||||
"""
|
"""
|
||||||
movie = models.ForeignKey('Movie', related_name='sort', unique=True)
|
movie = models.OneToOneField('Movie', related_name='sort', primary_key=True)
|
||||||
|
|
||||||
title = models.CharField(max_length=1000)
|
title = models.CharField(max_length=1000)
|
||||||
director = models.TextField(blank=True)
|
director = models.TextField(blank=True)
|
||||||
|
@ -360,6 +453,8 @@ class MovieSort(models.Model):
|
||||||
trivia = models.IntegerField(blank=True)
|
trivia = models.IntegerField(blank=True)
|
||||||
connections = models.IntegerField(blank=True)
|
connections = models.IntegerField(blank=True)
|
||||||
|
|
||||||
|
rating = models.FloatField(blank=True)
|
||||||
|
votes = models.IntegerField(blank=True)
|
||||||
scenes = models.IntegerField(blank=True)
|
scenes = models.IntegerField(blank=True)
|
||||||
words = models.IntegerField(null=True, blank=True)
|
words = models.IntegerField(null=True, blank=True)
|
||||||
wpm = models.IntegerField(null=True, blank=True)
|
wpm = models.IntegerField(null=True, blank=True)
|
||||||
|
@ -391,9 +486,10 @@ class MovieSort(models.Model):
|
||||||
options = classmethod(options)
|
options = classmethod(options)
|
||||||
|
|
||||||
class AlternativeTitle(models.Model):
|
class AlternativeTitle(models.Model):
|
||||||
movie = models.ForeignKey(Movie, related_name='alternative_titles')
|
movie = models.ForeignKey(Movie, related_name='alternative_titles_all')
|
||||||
title = models.TextField()
|
title = models.TextField()
|
||||||
type = models.CharField(max_length=1000)
|
type = models.CharField(max_length=1000)
|
||||||
|
manual = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('title', )
|
ordering = ('title', )
|
||||||
|
@ -453,6 +549,7 @@ class Cast(models.Model):
|
||||||
role = models.CharField(max_length=200)
|
role = models.CharField(max_length=200)
|
||||||
character = models.CharField(max_length=200, blank=True)
|
character = models.CharField(max_length=200, blank=True)
|
||||||
position = models.IntegerField()
|
position = models.IntegerField()
|
||||||
|
manual = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('position', 'person__name_sort')
|
ordering = ('position', 'person__name_sort')
|
||||||
|
@ -460,11 +557,12 @@ class Cast(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s <> %s" % (self.person, self.movie)
|
return "%s <> %s" % (self.person, self.movie)
|
||||||
|
|
||||||
def link(self, movie, person, role, character, position):
|
def link(self, movie, person, role, character, position, manual=False):
|
||||||
q = self.objects.filter(movie=movie, person=person, role=role, character=character)
|
q = self.objects.filter(movie=movie, person=person, role=role, character=character)
|
||||||
if q.count() > 0:
|
if q.count() > 0:
|
||||||
link = q[0]
|
link = q[0]
|
||||||
link.position = position
|
link.position = position
|
||||||
|
link.manual = manual
|
||||||
link.save()
|
link.save()
|
||||||
else:
|
else:
|
||||||
link = self()
|
link = self()
|
||||||
|
@ -473,6 +571,7 @@ class Cast(models.Model):
|
||||||
link.role=role
|
link.role=role
|
||||||
link.character=character
|
link.character=character
|
||||||
link.position = position
|
link.position = position
|
||||||
|
link.manual = manual
|
||||||
link.save()
|
link.save()
|
||||||
return link
|
return link
|
||||||
link = classmethod(link)
|
link = classmethod(link)
|
||||||
|
@ -482,7 +581,7 @@ class Cast(models.Model):
|
||||||
|
|
||||||
class Country(models.Model):
|
class Country(models.Model):
|
||||||
name = models.CharField(max_length=200, unique=True)
|
name = models.CharField(max_length=200, unique=True)
|
||||||
movies = models.ManyToManyField(Movie, related_name='countries', through='MovieCountry')
|
movies = models.ManyToManyField(Movie, related_name='countries_all', through='MovieCountry')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
#!! adding this to ordering, breaks:
|
#!! adding this to ordering, breaks:
|
||||||
|
@ -502,6 +601,7 @@ class MovieCountry(models.Model):
|
||||||
movie = models.ForeignKey(Movie)
|
movie = models.ForeignKey(Movie)
|
||||||
country = models.ForeignKey(Country)
|
country = models.ForeignKey(Country)
|
||||||
position = models.IntegerField()
|
position = models.IntegerField()
|
||||||
|
manual = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('position', 'country')
|
ordering = ('position', 'country')
|
||||||
|
@ -526,7 +626,7 @@ class MovieCountry(models.Model):
|
||||||
|
|
||||||
class Language(models.Model):
|
class Language(models.Model):
|
||||||
name = models.CharField(max_length=200, unique=True)
|
name = models.CharField(max_length=200, unique=True)
|
||||||
movies = models.ManyToManyField(Movie, related_name='languages', through="MovieLanguage")
|
movies = models.ManyToManyField(Movie, related_name='languages_all', through="MovieLanguage")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name', )
|
ordering = ('name', )
|
||||||
|
@ -542,6 +642,7 @@ class MovieLanguage(models.Model):
|
||||||
movie = models.ForeignKey(Movie)
|
movie = models.ForeignKey(Movie)
|
||||||
language = models.ForeignKey(Language)
|
language = models.ForeignKey(Language)
|
||||||
position = models.IntegerField()
|
position = models.IntegerField()
|
||||||
|
manual = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('position', 'language')
|
ordering = ('position', 'language')
|
||||||
|
@ -566,7 +667,8 @@ class MovieLanguage(models.Model):
|
||||||
|
|
||||||
class Keyword(models.Model):
|
class Keyword(models.Model):
|
||||||
name = models.CharField(max_length=200, unique=True)
|
name = models.CharField(max_length=200, unique=True)
|
||||||
movies = models.ManyToManyField(Movie, related_name='keywords')
|
manual = models.BooleanField(default=False)
|
||||||
|
movies = models.ManyToManyField(Movie, related_name='keywords_all')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name', )
|
ordering = ('name', )
|
||||||
|
@ -581,7 +683,8 @@ class Keyword(models.Model):
|
||||||
|
|
||||||
class Genre(models.Model):
|
class Genre(models.Model):
|
||||||
name = models.CharField(max_length=200, unique=True)
|
name = models.CharField(max_length=200, unique=True)
|
||||||
movies = models.ManyToManyField(Movie, related_name='genres')
|
manual = models.BooleanField(default=False)
|
||||||
|
movies = models.ManyToManyField(Movie, related_name='genres_all')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name', )
|
ordering = ('name', )
|
||||||
|
@ -596,7 +699,8 @@ class Genre(models.Model):
|
||||||
|
|
||||||
class Location(models.Model):
|
class Location(models.Model):
|
||||||
name = models.CharField(max_length=200, unique=True)
|
name = models.CharField(max_length=200, unique=True)
|
||||||
movies = models.ManyToManyField(Movie, related_name='locations')
|
manual = models.BooleanField(default=False)
|
||||||
|
movies = models.ManyToManyField(Movie, related_name='locations_all')
|
||||||
#fixme: geo data
|
#fixme: geo data
|
||||||
|
|
||||||
lat_sw = models.FloatField(default=0)
|
lat_sw = models.FloatField(default=0)
|
||||||
|
@ -620,8 +724,9 @@ class Location(models.Model):
|
||||||
|
|
||||||
class Trivia(models.Model):
|
class Trivia(models.Model):
|
||||||
trivia = models.TextField()
|
trivia = models.TextField()
|
||||||
movie = models.ForeignKey(Movie, related_name='trivia')
|
manual = models.BooleanField(default=False)
|
||||||
position = models.IntegerField()
|
position = models.IntegerField()
|
||||||
|
movie = models.ForeignKey(Movie, related_name='trivia_all')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('position', )
|
ordering = ('position', )
|
||||||
|
@ -637,16 +742,17 @@ class Trivia(models.Model):
|
||||||
return trivia
|
return trivia
|
||||||
|
|
||||||
class Connection(models.Model):
|
class Connection(models.Model):
|
||||||
subject = models.ForeignKey(Movie, related_name='connections')
|
subject = models.ForeignKey(Movie, related_name='connections_all')
|
||||||
relation = models.CharField(max_length=512)
|
relation = models.CharField(max_length=512)
|
||||||
object = models.ForeignKey(Movie)
|
object = models.ForeignKey(Movie)
|
||||||
|
manual = models.BooleanField(default=False)
|
||||||
|
|
||||||
def get_or_create(model, subject, relation, object, reverse=True):
|
def get_or_create(model, subject, relation, object, reverse=True, manual=False):
|
||||||
q = model.objects.filter(subject=subject, relation=relation, object=object)
|
q = model.objects.filter(subject=subject, relation=relation, object=object)
|
||||||
if q.count() > 0:
|
if q.count() > 0:
|
||||||
o = q[0]
|
o = q[0]
|
||||||
else:
|
else:
|
||||||
o = model.objects.create(subject=subject, relation=relation, object=object)
|
o = model.objects.create(subject=subject, relation=relation, object=object, manual=manual)
|
||||||
o.save()
|
o.save()
|
||||||
if reverse:
|
if reverse:
|
||||||
_map = {
|
_map = {
|
||||||
|
@ -672,119 +778,11 @@ class Connection(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '%s %s %s' % (self.subject, self.relation, self.object)
|
return '%s %s %s' % (self.subject, self.relation, self.object)
|
||||||
|
|
||||||
class File(models.Model):
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
modified = models.DateTimeField(auto_now=True)
|
|
||||||
|
|
||||||
oshash = models.CharField(blank=True, unique=True, max_length=16)
|
|
||||||
sha1 = models.CharField(blank=True, unique=True, max_length=40)
|
|
||||||
md5 = models.CharField(blank=True, unique=True, max_length=32)
|
|
||||||
|
|
||||||
movie = models.ForeignKey('Movie', related_name="files", default=None)
|
|
||||||
|
|
||||||
computed_path = models.CharField(blank=True, max_length=2048)
|
|
||||||
size = models.IntegerField(default=-1)
|
|
||||||
duration = models.FloatField(default=-1)
|
|
||||||
|
|
||||||
video_codec = models.CharField(blank=True, max_length=256)
|
|
||||||
pixel_format = models.CharField(blank=True, max_length=256)
|
|
||||||
width = models.IntegerField(default=-1)
|
|
||||||
height = models.IntegerField(default=-1)
|
|
||||||
pixel_aspect_ratio = models.CharField(blank=True, max_length=256)
|
|
||||||
display_aspect_ratio = models.CharField(blank=True, max_length=256)
|
|
||||||
framerate = models.CharField(blank=True, max_length=256)
|
|
||||||
|
|
||||||
audio_codec = models.CharField(blank=True, max_length=256)
|
|
||||||
samplerate = models.IntegerField(default=-1)
|
|
||||||
channels = models.IntegerField(default=-1)
|
|
||||||
|
|
||||||
#computed values
|
|
||||||
bpp = models.FloatField(default=-1)
|
|
||||||
pixels = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
part = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
def get_or_create(model, oshash):
|
|
||||||
try:
|
|
||||||
f = model.objects.get(oshash=oshash)
|
|
||||||
except model.DoesNotExist:
|
|
||||||
f = model.objects.create(oshash=oshash)
|
|
||||||
f.save()
|
|
||||||
return f
|
|
||||||
get_or_create = classmethod(get_or_create)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return "%s (%s)" % (self.computed_path, self.oshash)
|
|
||||||
|
|
||||||
class Archive(models.Model):
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
modified = models.DateTimeField(auto_now=True)
|
|
||||||
name = models.CharField(max_length=255, unique=True)
|
|
||||||
users = models.ManyToManyField(User, related_name='archives')
|
|
||||||
|
|
||||||
class ArchiveFile(models.Model):
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
modified = models.DateTimeField(auto_now=True)
|
|
||||||
archive = models.ForeignKey(Archive)
|
|
||||||
movie_file = models.ForeignKey(File)
|
|
||||||
path = models.CharField(blank=True, max_length=2048)
|
|
||||||
|
|
||||||
objects = managers.ArchiveFileManager()
|
|
||||||
|
|
||||||
def update(self, data):
|
|
||||||
"""
|
|
||||||
only add, do not overwrite keys in movie_file
|
|
||||||
"""
|
|
||||||
for key in ('duration', 'video_codec', 'pixel_format', 'width', 'height',
|
|
||||||
'pixel_aspect_ratio', 'display_aspect_ratio', 'framerate',
|
|
||||||
'audio_codec', 'samplerate', 'channels', 'size', 'sha1', 'md5'):
|
|
||||||
if key in data and not getattr(self.movie_file, key):
|
|
||||||
setattr(self.movie_file, key, data[key])
|
|
||||||
self.path = data.get('path', '')
|
|
||||||
self.movie_file.save()
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def get_or_create(model, archive, oshash):
|
|
||||||
try:
|
|
||||||
f = model.objects.by_oshash(oshash=oshash)
|
|
||||||
except model.DoesNotExist:
|
|
||||||
f = model.objects.create()
|
|
||||||
f.movie_file = File.get_or_create(oshash)
|
|
||||||
f.archive = archive
|
|
||||||
f.save()
|
|
||||||
return f
|
|
||||||
get_or_create = classmethod(get_or_create)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return '%s (%s)' % (self.path, unicode(self.user))
|
|
||||||
|
|
||||||
class Subtitle(models.Model):
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
modified = models.DateTimeField(auto_now=True)
|
|
||||||
user = models.ForeignKey(User)
|
|
||||||
|
|
||||||
movie_file = models.ForeignKey(File)
|
|
||||||
language = models.CharField(max_length=16)
|
|
||||||
srt = models.TextField(blank=True)
|
|
||||||
|
|
||||||
def get_or_create(model, user, oshash, language):
|
|
||||||
q = model.objects.filter(movie_file__oshash=oshash, language=language, user=user)
|
|
||||||
if q.count() > 0:
|
|
||||||
s = q[0]
|
|
||||||
else:
|
|
||||||
movie_file = models.File.get_or_create(oshash=oshash)
|
|
||||||
s = model.objects.create(user=user, language=language, movie_file=movie_file)
|
|
||||||
s.save()
|
|
||||||
return s
|
|
||||||
get_or_create = classmethod(get_or_create)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return '%s.%s.srt' % (os.path.splitext(self.movie_file.computed_path)[0], self.language)
|
|
||||||
|
|
||||||
class Review(models.Model):
|
class Review(models.Model):
|
||||||
movie = models.ForeignKey('Movie', related_name='reviews')
|
movie = models.ForeignKey('Movie', related_name='reviews_all')
|
||||||
title = models.CharField(blank=True, max_length=2048)
|
title = models.CharField(blank=True, max_length=2048)
|
||||||
url = models.CharField(blank=True, max_length=2048)
|
url = models.CharField(blank=True, max_length=2048)
|
||||||
|
manual = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
@ -843,3 +841,158 @@ class ListItem(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'%s in %s' % (unicode(self.movie), unicode(self.list))
|
return u'%s in %s' % (unicode(self.movie), unicode(self.list))
|
||||||
|
|
||||||
|
def stream_path(f, size):
|
||||||
|
name = "%s.%s" % (size, 'ogv')
|
||||||
|
url_hash = f.oshash
|
||||||
|
return os.path.join('stream', url_hash[:2], url_hash[2:4], url_hash[4:6], name)
|
||||||
|
|
||||||
|
class File(models.Model):
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
oshash = models.CharField(blank=True, unique=True, max_length=16)
|
||||||
|
sha1 = models.CharField(blank=True, unique=True, max_length=40)
|
||||||
|
md5 = models.CharField(blank=True, unique=True, max_length=32)
|
||||||
|
|
||||||
|
movie = models.ForeignKey(Movie, related_name="files", default=None)
|
||||||
|
|
||||||
|
computed_path = models.CharField(blank=True, max_length=2048)
|
||||||
|
size = models.IntegerField(default=-1)
|
||||||
|
duration = models.FloatField(default=-1)
|
||||||
|
|
||||||
|
is_video = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
video_codec = models.CharField(blank=True, max_length=256)
|
||||||
|
pixel_format = models.CharField(blank=True, max_length=256)
|
||||||
|
width = models.IntegerField(default=-1)
|
||||||
|
height = models.IntegerField(default=-1)
|
||||||
|
pixel_aspect_ratio = models.CharField(blank=True, max_length=256)
|
||||||
|
display_aspect_ratio = models.CharField(blank=True, max_length=256)
|
||||||
|
framerate = models.CharField(blank=True, max_length=256)
|
||||||
|
|
||||||
|
audio_codec = models.CharField(blank=True, max_length=256)
|
||||||
|
samplerate = models.IntegerField(default=-1)
|
||||||
|
channels = models.IntegerField(default=-1)
|
||||||
|
|
||||||
|
#computed values
|
||||||
|
bpp = models.FloatField(default=-1)
|
||||||
|
pixels = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
part = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
#stream related fields
|
||||||
|
available = models.BooleanField(default=False)
|
||||||
|
stream128 = models.FileField(default=None, upload_to=lambda f, x: stream_path(f, '128'))
|
||||||
|
stream320 = models.FileField(default=None, upload_to=lambda f, x: stream_path(f, '320'))
|
||||||
|
stream640 = models.FileField(default=None, upload_to=lambda f, x: stream_path(f, '640'))
|
||||||
|
|
||||||
|
def save_chunk(self, chunk, name='video.ogv'):
|
||||||
|
if not self.available:
|
||||||
|
#FIXME: this should use stream128 or stream640 depending on configuration
|
||||||
|
video = getattr(self, 'stream128')
|
||||||
|
if not video:
|
||||||
|
video.save(name, ContentFile(chunk))
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
f = open(video.path, 'a')
|
||||||
|
f.write(chunk)
|
||||||
|
f.close()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
objects = managers.FileManager()
|
||||||
|
|
||||||
|
def get_or_create(model, oshash):
|
||||||
|
try:
|
||||||
|
f = model.objects.get(oshash=oshash)
|
||||||
|
except model.DoesNotExist:
|
||||||
|
f = model.objects.create(oshash=oshash)
|
||||||
|
f.save()
|
||||||
|
return f
|
||||||
|
get_or_create = classmethod(get_or_create)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return "%s (%s)" % (self.computed_path, self.oshash)
|
||||||
|
|
||||||
|
class Subtitle(models.Model):
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
user = models.ForeignKey(User)
|
||||||
|
|
||||||
|
file = models.ForeignKey(File, related_name="subtitles")
|
||||||
|
language = models.CharField(max_length=16)
|
||||||
|
srt = models.TextField(blank=True)
|
||||||
|
|
||||||
|
def get_or_create(model, user, oshash, language):
|
||||||
|
q = model.objects.filter(file__oshash=oshash, language=language, user=user)
|
||||||
|
if q.count() > 0:
|
||||||
|
s = q[0]
|
||||||
|
else:
|
||||||
|
f = models.File.get_or_create(oshash=oshash)
|
||||||
|
s = model.objects.create(user=user, language=language, file=f)
|
||||||
|
s.save()
|
||||||
|
return s
|
||||||
|
get_or_create = classmethod(get_or_create)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s.%s.srt' % (os.path.splitext(self.movie_file.computed_path)[0], self.language)
|
||||||
|
|
||||||
|
class Layer(models.Model):
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
user = models.ForeignKey(User)
|
||||||
|
movie = models.ForeignKey(Movie)
|
||||||
|
|
||||||
|
#seconds
|
||||||
|
time_in = models.FloatField(default=-1)
|
||||||
|
time_out = models.FloatField(default=-1)
|
||||||
|
|
||||||
|
type = models.CharField(blank=True, max_length=255)
|
||||||
|
value = models.TextField()
|
||||||
|
|
||||||
|
#location = models.ForeignKey('Location', default=None)
|
||||||
|
|
||||||
|
class Archive(models.Model):
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
public = models.BooleanField(default=False)
|
||||||
|
users = models.ManyToManyField(User, related_name='archives')
|
||||||
|
|
||||||
|
class ArchiveFile(models.Model):
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
modified = models.DateTimeField(auto_now=True)
|
||||||
|
archive = models.ForeignKey(Archive, related_name='files')
|
||||||
|
file = models.ForeignKey(File)
|
||||||
|
path = models.CharField(blank=True, max_length=2048)
|
||||||
|
|
||||||
|
objects = managers.ArchiveFileManager()
|
||||||
|
|
||||||
|
def update(self, data):
|
||||||
|
"""
|
||||||
|
only add, do not overwrite keys in file
|
||||||
|
"""
|
||||||
|
for key in ('duration', 'video_codec', 'pixel_format', 'width', 'height',
|
||||||
|
'pixel_aspect_ratio', 'display_aspect_ratio', 'framerate',
|
||||||
|
'audio_codec', 'samplerate', 'channels', 'size', 'sha1', 'md5'):
|
||||||
|
if key in data and not getattr(self.file, key):
|
||||||
|
setattr(self.file, key, data[key])
|
||||||
|
self.path = data.get('path', '')
|
||||||
|
self.file.save()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def get_or_create(model, archive, oshash):
|
||||||
|
try:
|
||||||
|
f = model.objects.by_oshash(oshash=oshash)
|
||||||
|
except model.DoesNotExist:
|
||||||
|
f = model.objects.create()
|
||||||
|
f.file = File.get_or_create(oshash)
|
||||||
|
f.archive = archive
|
||||||
|
f.save()
|
||||||
|
return f
|
||||||
|
get_or_create = classmethod(get_or_create)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s (%s)' % (self.path, unicode(self.user))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,9 @@ urlpatterns = patterns("oxdb.backend.views",
|
||||||
(r'^find', 'find'),
|
(r'^find', 'find'),
|
||||||
(r'^files/find', 'find_files'),
|
(r'^files/find', 'find_files'),
|
||||||
(r'^files/info', 'file_info'),
|
(r'^files/info', 'file_info'),
|
||||||
(r'^files/(?P<archive>.+)/add', 'add_file'),
|
(r'^archive/(?P<archive>.+)/add', 'add_file'),
|
||||||
(r'^files/(?P<archive>.+)/remove', 'remove_file'),
|
(r'^archive/(?P<archive>.+)/remove', 'remove_file'),
|
||||||
|
(r'^file/parse', 'file_parse'),
|
||||||
(r'^subtitle/get', 'subtitles'),
|
(r'^subtitle/get', 'subtitles'),
|
||||||
(r'^preferences', 'preferences'),
|
(r'^preferences', 'preferences'),
|
||||||
|
|
||||||
|
|
103
backend/utils.py
103
backend/utils.py
|
@ -22,3 +22,106 @@ def oxid(title, director, year='', seriesTitle='', episodeTitle='', season=0, ep
|
||||||
oxid += hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()[:20]
|
oxid += hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()[:20]
|
||||||
return u"0x" + oxid
|
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][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'])
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
119
backend/views.py
119
backend/views.py
|
@ -12,14 +12,14 @@ 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.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.utils import simplejson as json
|
||||||
from oxdb.utils.shortcuts import render_to_json_response
|
from oxdb.utils.shortcuts import render_to_json_response
|
||||||
|
|
||||||
import models
|
import models
|
||||||
|
import utils
|
||||||
|
from decorators import login_required_json
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
field.length -> movie.sort.all()[0].field
|
field.length -> movie.sort.all()[0].field
|
||||||
o=0&n=100
|
o=0&n=100
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ a & b | c & d
|
||||||
|
|
||||||
query
|
query
|
||||||
|
|
||||||
|
|
||||||
l=user:name or l=name
|
l=user:name or l=name
|
||||||
q=year:1980,hello,country:usa
|
q=year:1980,hello,country:usa
|
||||||
q=year:1980,hello,country:!usa
|
q=year:1980,hello,country:!usa
|
||||||
|
@ -58,14 +57,25 @@ q=year:<1960,year:>1950,title:sex
|
||||||
|
|
||||||
s=director:asc,year:desc default: director:asc,year:desc
|
s=director:asc,year:desc default: director:asc,year:desc
|
||||||
r=0:100 or r=100 or r=100: default: 0:100
|
r=0:100 or r=100 or r=100: default: 0:100
|
||||||
k=id,title,director,date,cast.length default: title,director,year,country
|
p=id,title,director,date,cast.length default: title,director,year,country
|
||||||
|
q
|
||||||
|
|
||||||
|
List data backend spec:
|
||||||
|
url = //url for request
|
||||||
|
params = [] //additional params passed to url, i.e. query, or group
|
||||||
|
|
||||||
id=0133093
|
the url must understand the following requests:
|
||||||
|
number of items:
|
||||||
|
url?params&n=1
|
||||||
|
> {items: N}
|
||||||
|
items sorted by key range X to Y:
|
||||||
|
url?params&s=key:asc|desc&r=X:Y
|
||||||
|
> {items: [{k0:v0, k1:v1...}, {k0:v0, k1:v1...}]}
|
||||||
|
|
||||||
/json/find?l=all&s=date&f=all&q=&a=desc&p=id,title,director,date,cast.length
|
Examples:
|
||||||
|
/json/find?l=all&s=title&f=all&q=&a=desc&p=id,title,director,date,cast.length
|
||||||
|
|
||||||
/json/find?o=0&n=100&l=all&s=date&f=all&q=&a=desc&p=id,title,director,date,cast.length
|
/json/find?r=0:100&l=all&s=title&f=all&q=&a=desc&p=id,title,director,date,cast.length
|
||||||
{
|
{
|
||||||
movies=[
|
movies=[
|
||||||
{
|
{
|
||||||
|
@ -77,7 +87,7 @@ id=0133093
|
||||||
}
|
}
|
||||||
|
|
||||||
#get sort order for all ids
|
#get sort order for all ids
|
||||||
/json/find?o=0&n=1000&l=all&s=date&f=all&q=&a=desc&p=id
|
/json/find?r=0:1000&l=all&s=title&f=all&q=&a=desc&p=id
|
||||||
{
|
{
|
||||||
movies=[
|
movies=[
|
||||||
{
|
{
|
||||||
|
@ -86,7 +96,7 @@ id=0133093
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/json/find?l=all&s=date&f=all&q=&a=desc
|
/json/find?l=all&s=title&f=all&q=&a=desc
|
||||||
{
|
{
|
||||||
movies: 1234,
|
movies: 1234,
|
||||||
files: 2345,
|
files: 2345,
|
||||||
|
@ -96,7 +106,7 @@ id=0133093
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/json/find?o=0&n=100&l=all&s=[name, items]&f=all&q=&a=desc&g=country
|
/json/find?r=0:100&l=all&s=[name, items]&f=all&q=&a=desc&g=country
|
||||||
{
|
{
|
||||||
groups = [ {name:"USA", movies: 123}, {name:"UK", movies: 1234} ]
|
groups = [ {name:"USA", movies: 123}, {name:"UK", movies: 1234} ]
|
||||||
}
|
}
|
||||||
|
@ -106,13 +116,13 @@ id=0133093
|
||||||
#auto compleat in find box
|
#auto compleat in find box
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def order_query(qs, s, prefix='sort__'):
|
def order_query(qs, s, prefix='sort__'):
|
||||||
order_by = []
|
order_by = []
|
||||||
for e in s.split(','):
|
for e in s.split(','):
|
||||||
o = e.split(':')
|
o = e.split(':')
|
||||||
if len(o) == 1: o.append('asc')
|
if len(o) == 1: o.append('asc')
|
||||||
order = '%s%s' % (prefix, o[0])
|
order = {'id': 'movieId'}.get(o[0], o[0])
|
||||||
|
order = '%s%s' % (prefix, order)
|
||||||
if o[1] == 'desc':
|
if o[1] == 'desc':
|
||||||
order = '-%s' % order
|
order = '-%s' % order
|
||||||
order_by.append(order)
|
order_by.append(order)
|
||||||
|
@ -129,9 +139,9 @@ def parse_query(request):
|
||||||
def parse_dict(s):
|
def parse_dict(s):
|
||||||
d = s.split(",")
|
d = s.split(",")
|
||||||
return [i.strip() for i in d]
|
return [i.strip() for i in d]
|
||||||
_dicts = ['k', ]
|
_dicts = ['p', ]
|
||||||
_ints = ['o', 'n']
|
_ints = ['n', ]
|
||||||
for key in ('s', 'k', 'g', 'l'):
|
for key in ('s', 'p', 'g', 'l', 'n'):
|
||||||
if key in get:
|
if key in get:
|
||||||
if key in _ints:
|
if key in _ints:
|
||||||
query[key] = int(get[key])
|
query[key] = int(get[key])
|
||||||
|
@ -147,25 +157,31 @@ def parse_query(request):
|
||||||
if r[1] == '': r[0] = -1
|
if r[1] == '': r[0] = -1
|
||||||
query['i'] = int(r[0])
|
query['i'] = int(r[0])
|
||||||
query['o'] = int(r[1])
|
query['o'] = int(r[1])
|
||||||
|
#group by only allows sorting by name or number of itmes
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def find(request):
|
def find(request):
|
||||||
query = parse_query(request)
|
query = parse_query(request)
|
||||||
response = {}
|
response = {}
|
||||||
if 'k' in query:
|
if 'p' in query:
|
||||||
response['movies'] = []
|
response['items'] = []
|
||||||
qs = order_query(query['q'], query['s'])
|
qs = order_query(query['q'], query['s'])
|
||||||
qs = qs[query['i']:query['o']]
|
if 'n' in query:
|
||||||
p = Paginator(qs, 100)
|
response = {'items': qs.count()}
|
||||||
for i in p.page_range:
|
else:
|
||||||
page = p.page(i)
|
qs = qs[query['i']:query['o']]
|
||||||
for m in page.object_list:
|
p = Paginator(qs, 100)
|
||||||
response['movies'].append(m.json(query['k']))
|
for i in p.page_range:
|
||||||
|
page = p.page(i)
|
||||||
|
for m in page.object_list:
|
||||||
|
response['items'].append(m.json(query['p']))
|
||||||
elif 'g' in query:
|
elif 'g' in query:
|
||||||
|
if query['s'].split(':')[0] not in ('name', 'items'):
|
||||||
|
query['s'] = 'name'
|
||||||
#FIXME: also filter lists here
|
#FIXME: also filter lists here
|
||||||
response['groups'] = []
|
response['items'] = []
|
||||||
name = 'name'
|
name = 'name'
|
||||||
movies = 'movies'
|
items = 'movies'
|
||||||
movie_qs = query['q']
|
movie_qs = query['q']
|
||||||
_objects = {
|
_objects = {
|
||||||
'country': models.Country.objects,
|
'country': models.Country.objects,
|
||||||
|
@ -176,18 +192,29 @@ def find(request):
|
||||||
if query['g'] in _objects:
|
if query['g'] in _objects:
|
||||||
qs = _objects[query['g']].filter(movies__id__in=movie_qs).values('name').annotate(movies=Count('movies'))
|
qs = _objects[query['g']].filter(movies__id__in=movie_qs).values('name').annotate(movies=Count('movies'))
|
||||||
elif query['g'] == "year":
|
elif query['g'] == "year":
|
||||||
qs = movie_qs.values('year').annotate(movies=Count('id'))
|
qs = movie_qs.values('imdb__year').annotate(movies=Count('id'))
|
||||||
name='year'
|
name='imdb__year'
|
||||||
qs = order_query(qs, query['s'], '')
|
if 'n' in query:
|
||||||
qs = qs[query['i']:query['o']]
|
response['items'] = qs.count()
|
||||||
for i in qs:
|
else:
|
||||||
group = {'name': i[name], 'movies': i[movies]}
|
#replace normalized items/name sort with actual db value
|
||||||
response['groups'].append(group)
|
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']]
|
||||||
|
for i in qs:
|
||||||
|
group = {'title': i[name], 'items': i[items]}
|
||||||
|
response['items'].append(group)
|
||||||
else:
|
else:
|
||||||
#FIXME: also filter lists here
|
#FIXME: also filter lists here
|
||||||
movies = models.Movie.objects.all()
|
movies = models.Movie.objects.filter(available=True)
|
||||||
files = models.MovieFile.objects.all()
|
files = models.File.objects.all()
|
||||||
response['movies'] = movies.count()
|
response['items'] = movies.count()
|
||||||
response['files'] = files.count()
|
response['files'] = files.count()
|
||||||
r = files.aggregate(Count('size'), Count('pixels'), Count('duration'))
|
r = files.aggregate(Count('size'), Count('pixels'), Count('duration'))
|
||||||
response['pixels'] = r['pixels__count']
|
response['pixels'] = r['pixels__count']
|
||||||
|
@ -260,7 +287,7 @@ GET list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
@login_required
|
@login_required_json
|
||||||
def list_files(request):
|
def list_files(request):
|
||||||
response['files'] = {}
|
response['files'] = {}
|
||||||
qs = models.UserFile.filter(user=request.user)
|
qs = models.UserFile.filter(user=request.user)
|
||||||
|
@ -284,7 +311,7 @@ def find_files(request):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
POST add
|
POST add
|
||||||
> {
|
> file: {
|
||||||
"duration": 5.266667,
|
"duration": 5.266667,
|
||||||
"video_codec": "mpeg1",
|
"video_codec": "mpeg1",
|
||||||
"pixel_format": "yuv420p",
|
"pixel_format": "yuv420p",
|
||||||
|
@ -303,12 +330,14 @@ POST add
|
||||||
"md5":..
|
"md5":..
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
@login_required
|
#@login_required_json
|
||||||
def add_file(request, archive):
|
def add_file(request, archive):
|
||||||
oshash = request.POST['oshash']
|
print request.POST
|
||||||
|
info = json.loads(request.POST['file'])
|
||||||
|
oshash = info['oshash']
|
||||||
archive = models.Archive.objects.get(name=archive)
|
archive = models.Archive.objects.get(name=archive)
|
||||||
if archive.users.filter(user=request.user).count() == 1:
|
if archive.users.filter(user=request.user).count() == 1:
|
||||||
user_file = models.ArchiveFiles.get_or_create(request.user, oshash)
|
user_file = models.ArchiveFile.get_or_create(archive, oshash)
|
||||||
user_file.update(request.POST)
|
user_file.update(request.POST)
|
||||||
response = {'status': 200}
|
response = {'status': 200}
|
||||||
else:
|
else:
|
||||||
|
@ -318,7 +347,7 @@ def add_file(request, archive):
|
||||||
'''
|
'''
|
||||||
POST remove?oshash=
|
POST remove?oshash=
|
||||||
'''
|
'''
|
||||||
@login_required
|
@login_required_json
|
||||||
def remove_file(request, archive):
|
def remove_file(request, archive):
|
||||||
oshash = request.POST['oshash']
|
oshash = request.POST['oshash']
|
||||||
archive = models.Archive.objects.get(name=archive)
|
archive = models.Archive.objects.get(name=archive)
|
||||||
|
@ -326,11 +355,15 @@ def remove_file(request, archive):
|
||||||
response = {'status': 200}
|
response = {'status': 200}
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
|
|
||||||
|
def file_parse(request):
|
||||||
|
response = utils.parsePath(request.POST['path'])
|
||||||
|
return render_to_json_response(response)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
POST preferences/get?key=
|
POST preferences/get?key=
|
||||||
POST preferences/set?key=&value
|
POST preferences/set?key=&value
|
||||||
'''
|
'''
|
||||||
@login_required
|
@login_required_json
|
||||||
def preferences(request):
|
def preferences(request):
|
||||||
oshash = request.POST['oshash']
|
oshash = request.POST['oshash']
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -43,6 +43,7 @@ USE_I18N = True
|
||||||
# Example: "/home/media/media.lawrence.com/"
|
# Example: "/home/media/media.lawrence.com/"
|
||||||
MEDIA_ROOT = join(PROJECT_ROOT, 'media')
|
MEDIA_ROOT = join(PROJECT_ROOT, 'media')
|
||||||
STATIC_ROOT = join(PROJECT_ROOT, 'static')
|
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
|
# 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).
|
||||||
|
@ -80,9 +81,9 @@ INSTALLED_APPS = (
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.humanize',
|
'django.contrib.humanize',
|
||||||
'south',
|
# 'south',
|
||||||
|
|
||||||
'oxdb.backend',
|
'backend',
|
||||||
)
|
)
|
||||||
|
|
||||||
#overwrite default settings with local settings
|
#overwrite default settings with local settings
|
||||||
|
|
37
static/css/ui.css
Normal file
37
static/css/ui.css
Normal 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;
|
||||||
|
}
|
1127
static/js/ui.js
Normal file
1127
static/js/ui.js
Normal file
File diff suppressed because it is too large
Load diff
BIN
static/png/frame.png
Normal file
BIN
static/png/frame.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
static/png/timeline.png
Normal file
BIN
static/png/timeline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
17
templates/index.html
Normal file
17
templates/index.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>0xdb.org</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<script type="text/javascript" src="/static/js/jquery/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/oxjs/js/ox.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/oxjs/js/ox.ui.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/oxjs/js/ox.iso.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/oxjs/js/ox.unicode.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/ui.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/oxjs/css/ox.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/ui.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
urls.py
3
urls.py
|
@ -10,6 +10,7 @@ admin.autodiscover()
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
# Example:
|
# Example:
|
||||||
(r'^json/', include('backend.urls')),
|
(r'^json/', include('backend.urls')),
|
||||||
|
(r'^$', 'app.views.index'),
|
||||||
|
|
||||||
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
|
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
|
||||||
# to INSTALLED_APPS to enable admin documentation:
|
# to INSTALLED_APPS to enable admin documentation:
|
||||||
|
@ -25,6 +26,8 @@ if settings.DEBUG:
|
||||||
{'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}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
15
utils/shortcuts.py
Normal file
15
utils/shortcuts.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.utils import simplejson
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
def render_to_json_response(dictionary, content_type="text/json"):
|
||||||
|
indent=None
|
||||||
|
if settings.DEBUG:
|
||||||
|
content_type = "text/javascript"
|
||||||
|
indent = 2
|
||||||
|
return HttpResponse(simplejson.dumps(dictionary, indent=indent), content_type=content_type)
|
||||||
|
|
Loading…
Reference in a new issue