diff --git a/backend/load.py b/backend/load.py index 0e278e64..2506b8ff 100644 --- a/backend/load.py +++ b/backend/load.py @@ -59,25 +59,34 @@ def loadIMDb(imdbId): setattr(movie, key, business[key]) movie.save() + models.AlternativeTitle.objects.filter(movie=movie).delete() + for i in oxweb.imdb.getMovieAKATitles(imdbId): + t = models.AlternativeTitle() + t.movie = movie + t.title = i[0] + t.type = i[1] + t.save() #FIXME: related tables should be cleaned to not accumulate cruft #Country models.MovieCountry.objects.filter(movie=movie).delete() position = 0 - for i in info['country']: - debug("add country", i) - country = models.Country.get_or_create(i) - models.MovieCountry.link(movie, country, position) - position += 1 + if 'country' in info: + for i in info['country']: + debug("add country", i) + country = models.Country.get_or_create(i) + models.MovieCountry.link(movie, country, position) + position += 1 #Language models.MovieLanguage.objects.filter(movie=movie).delete() position = 0 - for i in info['language']: - debug("add language", i) - language = models.Language.get_or_create(i) - models.MovieLanguage.link(movie, language, position) - position += 1 + if 'language' in info: + for i in info['language']: + debug("add language", i) + language = models.Language.get_or_create(i) + models.MovieLanguage.link(movie, language, position) + position += 1 #Location movie.locations.all().delete() @@ -89,10 +98,11 @@ def loadIMDb(imdbId): #Genre movie.genres.all().delete() - for i in info['genre']: - debug("add genre", i) - genre = models.Genre.get_or_create(i) - genre.movies.add(movie) + if 'genre' in info: + for i in info['genre']: + debug("add genre", i) + genre = models.Genre.get_or_create(i) + genre.movies.add(movie) #Keyword movie.keywords.all().delete() @@ -106,7 +116,7 @@ def loadIMDb(imdbId): position = 0 trivia = oxweb.imdb.getMovieTrivia(imdbId) for i in trivia: - debug("add trivia", g) + debug("add trivia", i) t = models.Trivia() t.movie = movie t.trivia = i @@ -127,8 +137,16 @@ def loadIMDb(imdbId): models.Cast.link(movie, person, role, character, position) position += 1 - #FIXME: connections - #m.addMovieConnections(IMDb['connections']) + movie.connections.all().delete() + connections = oxweb.imdb.getMovieConnections(imdbId) + for relation in connections: + for otherId in connections[relation]: + try: + object = models.Movie.objects.get(imdbId=otherId) + debug("add connection", relation, object) + models.Connection.get_or_create(movie, relation, object) + except models.Movie.DoesNotExist: + pass reviews = oxweb.imdb.getMovieExternalReviews(imdbId) for r in reviews: diff --git a/backend/managers.py b/backend/managers.py index 42ae3042..01539107 100644 --- a/backend/managers.py +++ b/backend/managers.py @@ -1,29 +1,136 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +import re +from datetime import datetime +from urllib2 import unquote + from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist -from django.db import models -from django.db.models import Q +from django.db.models import Q, Manager +import models -class MovieManager(models.Manager): +def keyType(key): + if key in ('released'): + return "date" + if key in ('year', 'cast.length'): + return "int" + if key in ('rating', 'votes'): + return "float" + return "string" + +class MovieManager(Manager): def get_query_set(self): return super(MovieManager, self).get_query_set() - def find(self, q="", f="all", s="title", a="desc", l="all", o=0, n=100, p=None): - qs = self.get_query_set() - - if q: - if f == "all": - qs = qs.filter(title__icontains=q) + def find(self, request): + ''' + construct query set from q value in request, + also checks for lists. + range and order must be applied later + ''' + for i in request.META['QUERY_STRING'].split('&'): + if i.startswith('q='): + q = i[2:] + op = ',' + if '|' in q: + op = '|' + conditions = [] + for e in q.split(op): + e = e.split(':') + if len(e) == 1: e = ['all'] + e + k, v = e + exclude = False + if v.startswith('!'): + v = v[1:] + exclude = True + if keyType(k) == "string": + startswith = v.startswith('^') + endswith = v.endswith('$') + if startswith and endswith: + v = v[1:-1] + k = '%s__iexact' % k + elif startswith: + v = v[1:] + k = '%s__istartswith' % k + elif v.endswith('$'): + v = v[:-1] + k = '%s__iendswith' % k + else: + k = '%s__icontains' % k + k = 'find__%s' % k + v = unquote(v) + if exclude: + conditions.append(~Q(**{k:v})) + else: + conditions.append(Q(**{k:v})) else: - field = str("find__%s__icontains"%f) - qs = qs.filter(**{field: q}) + def parseDate(d): + while len(d) < 3: + d.append(1) + return datetime(*[int(i) for i in d]) + #1960-1970 + match = re.compile("(-?[\d\.]+?)-(-?[\d\.]+$)").findall(v) + if match: + v1 = match[0][0] + v2 = match[0][1] + if keyType(k) == "date": + v1 = parseDate(v1.split('.')) + v2 = parseDate(v2.split('.')) + if exclude: #!1960-1970 + k1 = str('%s__lt' % k) + k2 = str('%s__gte' % k) + conditions.append(Q(**{k1:v1})|Q(**{k2:v2})) + else: #1960-1970 + k1 = str('%s__gte' % k) + k2 = str('%s__lt' % k) + conditions.append(Q(**{k1:v1})&Q(**{k2:v2})) + else: + if keyType(k) == "date": + v = parseDate(v.split('.')) + k = str('%s' % k) + if exclude: #!1960 + conditions.append(~Q(**{k:v})) + else: #1960 + conditions.append(Q(**{k:v})) - order_by = s - if a == "desc": - order_by = "-sort__" + order_by - qs = qs.order_by(order_by) + #join query with operator + qs = self.get_query_set() + if conditions: + q = conditions[0] + for c in conditions[1:]: + if op == '|': + q = q | c + else: + q = q & c + qs = qs.filter(q) - return qs[o:n] + # filter list, works for own or public lists + l = request.GET.get('l', 'all') + if l != "all": + l = l.split(":") + only_public = True + if not request.user.is_anonymous(): + if len(l) == 1: l = [request.user.username] + l + if request.user.username == l[0]: + only_public = False + if len(l) == 2: + lqs = models.List.objects.filter(name=l[1], user__username=l[0]) + if only_public: + lqs = qls.filter(public=True) + if lqs.count() == 1: + qs = qs.filter(listitem__list__id=lqs[0].id) + return qs + +class ArchiveFileManager(Manager): + def get_query_set(self): + return super(UserFileManager, self).get_query_set() + + def by_oshash(self, oshash): + q = self.get_query_set() + q.filter(movie_file__oshash=oshash) + if q.count() == 0: + raise models.UserFile.DoesNotExist("%s matching oshash %s does not exist." % + (models.UserFile._meta.object_name, oshash)) + return q[0] diff --git a/backend/models.py b/backend/models.py index 0453dd84..7db164f7 100644 --- a/backend/models.py +++ b/backend/models.py @@ -1,11 +1,14 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +import re import os.path from django.db import models from django.db.models import Q from django.contrib.auth.models import User import oxlib +from oxlib import stripTags +from oxlib.normalize import canonicalTitle, canonicalName import utils import managers @@ -61,11 +64,19 @@ class Movie(models.Model): #FIXME: include role and character def cast(self): cast = [] - for c in Cast.objects.filter(movie=self, role=cast).order_by('position'): + for c in Cast.objects.filter(movie=self, role='cast').order_by('position'): cast.append((c.person.name, c.character)) return tuple(cast) #return self.person.filter(cast__role='cast').order_by('cast__position') + def connections_json(self): + connections = {} + for connection in self.connections.all(): + if connection.relation not in connections: + connections[connection.relation] = [] + connections[connection.relation].append(connection.object.movieId) + return connections + def filtered_reviews(self): whitelist = ReviewWhitelist.objects.all() q = Q(id=-1) @@ -73,22 +84,14 @@ class Movie(models.Model): q = q | Q(url__contains=w.url) return self.reviews.filter(q) - risk = models.IntegerField(null=True, blank=True) - rights_level = models.IntegerField(null=True, blank=True) - rights_text = models.TextField(blank=True) - - #title_english = models.TextField(blank=True) - #FIXME: join AltTitle - - #FIXME: use mapping + rights_level = models.IntegerField(default=-1) + + #FIXME: use data.0xdb.org tpb_id = models.CharField(max_length=128, blank=True) kg_id = models.CharField(max_length=128, blank=True) open_subtitle_id = models.IntegerField(null=True, blank=True) wikipedia_url = models.TextField(blank=True) - #FIXME: join Location - #locations = models.TextField(blank=True) - #Series information series_imdb = models.CharField(max_length=7, default='') series_title = models.TextField(blank=True, default='') @@ -107,7 +110,7 @@ class Movie(models.Model): scene_height = models.IntegerField(null=True, blank=True) def __unicode__(self): - return "%s (%s)" % (self.title, self.year) + return u'%s (%s)' % (self.title, self.year) def save(self, *args, **kwargs): if self.imdbId: @@ -115,9 +118,10 @@ class Movie(models.Model): else: mid = self.oxdbId self.movieId = mid - #FIXME: update sort and find values here super(Movie, self).save(*args, **kwargs) + self.updateFind() + self.updateSort() _public_fields = { 'movieId': 'id', @@ -138,6 +142,8 @@ class Movie(models.Model): 'episode': 'episode', 'filtered_reviews': 'reviews', 'trivia': 'trivia', + 'alternative_titles': 'alternative_titles', + 'connections': 'connections_json' } def json(self, fields=None): movie = {} @@ -147,12 +153,25 @@ class Movie(models.Model): value = getattr(self, key) if key in ('directors', 'writers', 'filtered_reviews'): movie[pub_key] = tuple([v.json() for v in value()]) - elif key in ('countries', 'keywords', 'genres', 'trivia'): + elif key in ('countries', 'keywords', 'genres', 'trivia', 'alternative_titles'): movie[pub_key] = tuple([v.json() for v in value.all()]) else: movie[pub_key] = value + for f in fields: + if f.endswith('.length') and f[:-7] in ('cast', 'genre', 'trivia'): + movie[f] = getattr(self.sort.all()[0], f[:-7]) return movie + def fields(self): + fields = {} + for f in self._meta.fields: + if f.name in self._public_fields: + fields[f.name] = {} + fields[f.name]['order'] = 'desc' + fields[f.name]['type'] = type(f) + return fields + fields = classmethod(fields) + #Class functions to get Movies by ID, right now movieId, imdbId and oxdbId #FIXME: this should go into a manager def byMovieId(self, movieId): @@ -162,29 +181,124 @@ class Movie(models.Model): byMovieId = classmethod(byMovieId) def byImdbId(self, imdbId): - q = self.objects.filter(imdbId=imdbId) - if q.count() == 0: - raise self.DoesNotExist("%s matching imdb id %s does not exist." % (self._meta.object_name, imdbId)) - return q[0] + return self.objects.get(imdbId=imdbId) byImdbId = classmethod(byImdbId) def byOxdbId(self, oxdbId): - q = self.objects.filter(oxdbId=oxdbId) - if q.count() == 0: - raise self.DoesNotExist("%s matching oxdb id %s does not exist." % (self._meta.object_name, oxdbId)) - return q[0] + return self.objects.get(oxdbId=oxdbId) byOxdbId = classmethod(byOxdbId) def oxid(self): - directors = ",".join([d.name for d in self.directors()]) - return utils.oxid(self.title, directors, self.year, self.series_title, self.episode_title, self.season, self.episode) + directors = ','.join([d.name for d in self.directors()]) + return utils.oxid(self.title, directors, self.year, + self.series_title, self.episode_title, self.season, self.episode) + + def updateFind(self): + if self.find.count() == 0: + f = MovieFind() + f.movie = self + else: + f = self.find.all()[0] + f.title = self.title + ' '.join([t.title for t in self.alternative_titles.all()]) + f.director = ' '.join([i.name for i in self.directors()]) + f.country = ' '.join([i.name for i in self.countries.all()]) + f.year = self.year + f.language = ' '.join([i.name for i in self.languages.all()]) + f.writer = ' '.join([i.name for i in self.writers()]) + f.producer = ' '.join([i.name for i in self.producers()]) + f.editor = ' '.join([i.name for i in self.editors()]) + f.cinematographer = ' '.join([i.name for i in self.cinematographers()]) + f.cast = ' '.join(['%s %s' % i for i in self.cast()]) + f.genre = ' '.join([i.name for i in self.genres.all()]) + f.keywords = ' '.join([i.name for i in self.keywords.all()]) + f.summary = self.plot + self.plot_outline + f.trivia = ' '.join([i.trivia for i in self.trivia.all()]) + f.location = ' '.join([i.name for i in self.locations.all()]) + f.filename = self.filename + f.all = ' '.join([f.title, f.director, f.country, f.year, f.language, + f.writer, f.producer, f.editor, f.cinematographer, + f.cast, f.genre, f.keywords, f.summary, f.trivia, + f.location, f.filename]) + f.save() + + def updateSort(self): + if self.sort.count() == 0: + s = MovieSort() + s.movie = self + else: + s = self.sort.all()[0] + + def sortName(value): + sort_value = '~' + if value: + sort_value = stripTags(value).split(',') + sort_value = '; '.join([canonicalName(name.strip()) for name in sort_value]) + sort_value = sort_value.replace(u'\xc5k', 'A') + if not sort_value: + sort_value = '~' + return sort_value + + #title + title = canonicalTitle(self.title) + title = re.sub(u'[\'!¿¡,\.;\-"\:\*\[\]]', '', title) + title = title.replace(u'Æ', 'Ae') + #pad numbered titles + numbers = re.compile('^(\d+)').findall(title) + if numbers: + padded = '%010d' % int(numbers[0]) + title = re.sub('^(\d+)', padded, title) + + s.title = title.strip() + + directors = ','.join([i.name for i in self.directors()]) + s.director = sortName(directors) + + s.country = ','.join([i.name for i in self.countries.all()]) + s.year = self.year + + names = ','.join([i.name for i in self.producers()]) + s.producer = sortName(names) + names = ','.join([i.name for i in self.writers()]) + s.writer = sortName(names) + names = ','.join([i.name for i in self.editors()]) + s.editor = sortName(names) + names = ','.join([i.name for i in self.cinematographers()]) + s.cinematographer = sortName(names) + + s.country = ','.join([i.name for i in self.languages.all()]) + s.runtime = self.runtime + + s.keywords = self.keywords.all().count() + s.genre = self.genres.all().count() + s.cast = len(self.cast()) + s.summary = len(self.plot.split()) + s.trivia = self.trivia.all().count() + s.connections = self.connections.all().count() + s.movieId = self.movieId + + # data from related subtitles + s.scenes = 0 #FIXME + s.words = 0 #FIXME + s.wpm = 0 #FIXME + s.risk = 0 #FIXME + # data from related files + s.duration = 0 #FIXME + s.resolution = 0 #FIXME + s.aspectratio = 0 #FIXME + s.bitrate = 0 #FIXME + s.pixels = 0 #FIXME + s.filename = 0 #FIXME + s.files = 0 #FIXME + s.size = 0 #FIXME + s.save() -''' - used to search movies, all search values are in here -''' class MovieFind(models.Model): - movie = models.ForeignKey('Movie', related_name='find') + """ + used to search movies, all search values are in here + """ + movie = models.ForeignKey('Movie', related_name='find', unique=True) + all = models.TextField(blank=True) title = models.CharField(max_length=1000) director = models.TextField(blank=True) country = models.TextField(blank=True) @@ -193,8 +307,8 @@ class MovieFind(models.Model): writer = models.TextField(blank=True) producer = models.TextField(blank=True) editor = models.TextField(blank=True) - cinematographers = models.TextField(blank=True) - cast = models.IntegerField(blank=True) + cinematographer = models.TextField(blank=True) + cast = models.TextField(blank=True) #person genre = models.TextField(blank=True) @@ -202,7 +316,6 @@ class MovieFind(models.Model): summary = models.TextField(blank=True) trivia = models.TextField(blank=True) locations = models.TextField(blank=True) - connections = models.TextField(blank=True) #only for own files or as admin? filename = models.TextField(blank=True) @@ -221,11 +334,11 @@ class MovieFind(models.Model): return tuple(options) options = classmethod(options) -''' - used to sort movies, all sort values are in here -''' class MovieSort(models.Model): - movie = models.ForeignKey('Movie', related_name='sort') + """ + used to sort movies, all sort values are in here + """ + movie = models.ForeignKey('Movie', related_name='sort', unique=True) title = models.CharField(max_length=1000) director = models.TextField(blank=True) @@ -235,10 +348,10 @@ class MovieSort(models.Model): producer = models.TextField(blank=True) writer = models.TextField(blank=True) editor = models.TextField(blank=True) - cinematographers = models.TextField(blank=True) + cinematographer = models.TextField(blank=True) language = models.TextField(blank=True) - runtime = models.IntegerField(blank=True) + runtime = models.IntegerField(blank=True, null=True) keywords = models.IntegerField(blank=True) genre = models.TextField(blank=True) @@ -279,8 +392,8 @@ class MovieSort(models.Model): class AlternativeTitle(models.Model): movie = models.ForeignKey(Movie, related_name='alternative_titles') - type = models.CharField(max_length=128) title = models.TextField() + type = models.CharField(max_length=1000) class Meta: ordering = ('title', ) @@ -288,6 +401,9 @@ class AlternativeTitle(models.Model): def __unicode__(self): return self.title + def json(self): + return (self.title, self.type) + def get_or_create(model, name): try: o = model.objects.get(name=name) @@ -408,10 +524,9 @@ class MovieCountry(models.Model): return link link = classmethod(link) - class Language(models.Model): name = models.CharField(max_length=200, unique=True) - movies = models.ManyToManyField(Movie, related_name='language', through="MovieLanguage") + movies = models.ManyToManyField(Movie, related_name='languages', through="MovieLanguage") class Meta: ordering = ('name', ) @@ -464,7 +579,6 @@ class Keyword(models.Model): def json(self): return self.name - class Genre(models.Model): name = models.CharField(max_length=200, unique=True) movies = models.ManyToManyField(Movie, related_name='genres') @@ -485,6 +599,14 @@ class Location(models.Model): movies = models.ManyToManyField(Movie, related_name='locations') #fixme: geo data + lat_sw = models.FloatField(default=0) + lng_sw = models.FloatField(default=0) + lat_ne = models.FloatField(default=0) + lng_ne = models.FloatField(default=0) + lat_center = models.FloatField(default=0) + lng_center = models.FloatField(default=0) + area = models.FloatField(default=-1) + class Meta: ordering = ('name', ) @@ -508,17 +630,57 @@ class Trivia(models.Model): return self.trivia def json(self): - return self.trivia + trivia = self.trivia + trivia = oxlib.fixAmpersands(trivia) + trivia = re.sub('(.*?)', '\\2', trivia) + trivia = re.sub('(.*?)', '\\2', trivia) + return trivia -class MovieFile(models.Model): +class Connection(models.Model): + subject = models.ForeignKey(Movie, related_name='connections') + relation = models.CharField(max_length=512) + object = models.ForeignKey(Movie) + + def get_or_create(model, subject, relation, object, reverse=True): + q = model.objects.filter(subject=subject, relation=relation, object=object) + if q.count() > 0: + o = q[0] + else: + o = model.objects.create(subject=subject, relation=relation, object=object) + o.save() + if reverse: + _map = { + 'Edited into': 'Edited from', + 'Features': 'Featured in', + 'Follows': 'Followed by', + 'References': 'Referenced in', + 'Remake of': 'Remade as', + 'Spin off from': 'Spin off', + 'Spoofs': 'Spoofed in', + 'Version of': 'Version of', + } + if relation in _map.values(): + for k in _map: + if _map[k] == relation: + reverse_relation = k + else: + reverse_relation = _map[relation] + o2 = model.get_or_create(object, reverse_relation, subject, reverse=False) + return o + get_or_create = classmethod(get_or_create) + + def __unicode__(self): + 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) - sha1hash = models.CharField(blank=True, unique=True, max_length=40) - md5sum = models.CharField(blank=True, unique=True, max_length=32) + 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") + movie = models.ForeignKey('Movie', related_name="files", default=None) computed_path = models.CharField(blank=True, max_length=2048) size = models.IntegerField(default=-1) @@ -540,35 +702,87 @@ class MovieFile(models.Model): bpp = models.FloatField(default=-1) pixels = models.IntegerField(default=0) - part = models.IntegerField(default=1) + 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 UserFile(models.Model): +class Archive(models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) - user = models.ForeignKey(User) - movie_file = models.ForeignKey(MovieFile) + name = models.CharField(max_length=512, 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)) + 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(MovieFile) + 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) + return '%s.%s.srt' % (os.path.splitext(self.movie_file.computed_path)[0], self.language) class Review(models.Model): - movie = models.ForeignKey('Movie', related_name="reviews") + movie = models.ForeignKey('Movie', related_name='reviews') title = models.CharField(blank=True, max_length=2048) url = models.CharField(blank=True, max_length=2048) @@ -602,7 +816,8 @@ class List(models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) user = models.ForeignKey(User) - title = models.CharField(max_length=255, unique=True) + name = models.CharField(max_length=255, unique=True) + public = models.BooleanField(default=False) movies = models.ManyToManyField(Movie, related_name='lists', through='ListItem') def add(self, movie): @@ -617,7 +832,7 @@ class List(models.Model): self.ListItem.objects.all().filter(movie=movie, list=self).delete() def __unicode__(self): - return u"%s (%s)" % (self.title, unicode(self.user)) + return u'%s (%s)' % (self.title, unicode(self.user)) class ListItem(models.Model): created = models.DateTimeField(auto_now_add=True) @@ -626,5 +841,5 @@ class ListItem(models.Model): movie = models.ForeignKey(Movie) 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)) diff --git a/backend/urls.py b/backend/urls.py index 4ecfaaba..279e9554 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -24,9 +24,10 @@ urlpatterns = patterns("oxdb.backend.views", (r'^find', 'find'), (r'^files/find', 'find_files'), (r'^files/info', 'file_info'), - (r'^files/add', 'add_file'), - (r'^files/remove', 'remove_file'), - (r'^subtitle/get', 'get_subtitle'), + (r'^files/(?P.+)/add', 'add_file'), + (r'^files/(?P.+)/remove', 'remove_file'), + (r'^subtitle/get', 'subtitles'), + (r'^preferences', 'preferences'), # Example: # (r'^oxdata/', include('oxdata.foo.urls')), diff --git a/backend/views.py b/backend/views.py index 019f914a..247015f5 100644 --- a/backend/views.py +++ b/backend/views.py @@ -1,19 +1,69 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 import os.path +import re +from datetime import datetime +from urllib2 import unquote + from django.db import models from django.db.models import Q, Avg, Count from django.contrib.auth.models import User from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404 from django.template import RequestContext from django.core.paginator import Paginator +from django.contrib.auth.decorators import login_required from oxdb.utils.shortcuts import render_to_json_response import models + ''' -.length -> _sort + +field.length -> movie.sort.all()[0].field +o=0&n=100 + + +a & b | c & d + +query + + +l=user:name or l=name +q=year:1980,hello,country:usa +q=year:1980,hello,country:!usa +q=title:^the$ +q=title:^100%24$ +q=year:<1970,year:>1960 +q=year:<1960,year:>1950,title:sex + +!1960-1970 +2009.08.02.22.26.35-2009.08.02.22.26.35 + +!^the + + (dddd.dd.dd)-(dddd.dd.dd) + +5-8 +10000-20000 + +<2009-08-02-22-26-35 + +>2009-08-02-22-26-35 +2009-08-02-22-26-35< + +^the the* +*foo foo$ +*foo* foo + +s=director:asc,year:desc default: director:asc,year:desc +r=0:100 or r=100 or r=100: default: 0:100 +k=id,title,director,date,cast.length default: title,director,year,country + + +id=0133093 + +/json/find?l=all&s=date&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 { @@ -56,20 +106,32 @@ import models #auto compleat in find box ''' -def parse_query(get): + +def order_query(qs, s, prefix='sort__'): + order_by = [] + for e in s.split(','): + o = e.split(':') + if len(o) == 1: o.append('asc') + order = '%s%s' % (prefix, o[0]) + if o[1] == 'desc': + order = '-%s' % order + order_by.append(order) + if order_by: + qs = qs.order_by(*order_by) + return qs + +def parse_query(request): + get = request.GET query = {} - query["o"] = 0 - query["n"] = 100 - query["q"] = "The" - query["f"] = "all" - query["s"] = "title" - query["a"] = "desc" + query['i'] = 0 + query['o'] = 100 + query['s'] = 'title:asc' def parse_dict(s): d = s.split(",") return [i.strip() for i in d] - _dicts = ['p', ] + _dicts = ['k', ] _ints = ['o', 'n'] - for key in ('q', 'f', 's', 'a', 'p', 'g', 'o', 'n'): + for key in ('s', 'k', 'g', 'l'): if key in get: if key in _ints: query[key] = int(get[key]) @@ -77,44 +139,57 @@ def parse_query(get): query[key] = parse_dict(get[key]) else: query[key] = get[key] - print query + query['q'] = models.Movie.objects.find(request) + if 'r' in get: + r = get['r'].split(':') + if len(r) == 1: r.append(0) + if r[0] == '': r[0] = 0 + if r[1] == '': r[0] = -1 + query['i'] = int(r[0]) + query['o'] = int(r[1]) return query def find(request): - query = parse_query(request.GET) + query = parse_query(request) response = {} - if "p" in query: + if 'k' in query: response['movies'] = [] - - qs = models.Movie.objects.find(**query) + qs = order_query(query['q'], query['s']) + qs = qs[query['i']:query['o']] p = Paginator(qs, 100) for i in p.page_range: page = p.page(i) for m in page.object_list: - response['movies'].append(m.json(query['p'])) - elif "g" in query: + response['movies'].append(m.json(query['k'])) + elif 'g' in query: + #FIXME: also filter lists here response['groups'] = [] - name = "name" - movies = "movies" - if query["g"] == "country": - qs = models.Country.objects.values("name").annotate(movies=Count('movies')) - if query["g"] == "genre": - qs = models.Genre.objects.values("name").annotate(movies=Count('movies')) - if query["g"] == "language": - qs = models.Language.objects.values("name").annotate(movies=Count('movies')) - if query["g"] == "director": - qs = models.Person.objects.filter(cast__role="directors").values("name").annotate(movies=Count('movies')) - if query["g"] == "year": - qs = models.Movie.objects.values('year').annotate(movies=Count('id')) - name="year" - qs = qs[query['o']:query['n']] + name = 'name' + movies = 'movies' + movie_qs = query['q'] + _objects = { + 'country': models.Country.objects, + 'genre': models.Genre.objects, + 'language': models.Language.objects, + 'director': models.Person.objects.filter(cast__role='directors'), + } + if query['g'] in _objects: + qs = _objects[query['g']].filter(movies__id__in=movie_qs).values('name').annotate(movies=Count('movies')) + elif query['g'] == "year": + qs = movie_qs.values('year').annotate(movies=Count('id')) + name='year' + qs = order_query(qs, query['s'], '') + qs = qs[query['i']:query['o']] for i in qs: - group = {"name": i[name], "movies": i[movies]} + group = {'name': i[name], 'movies': i[movies]} response['groups'].append(group) else: - response['movies'] = models.Movie.objects.all().count() - response['files'] = models.MovieFile.objects.all().count() - r = models.MovieFile.objects.all().aggregate(Count('size'), Count('pixels'), Count('duration')) + #FIXME: also filter lists here + movies = models.Movie.objects.all() + files = models.MovieFile.objects.all() + response['movies'] = movies.count() + response['files'] = files.count() + r = files.aggregate(Count('size'), Count('pixels'), Count('duration')) response['pixels'] = r['pixels__count'] response['size'] = r['size__count'] response['duration'] = r['duration__count'] @@ -144,6 +219,9 @@ GET info?oshash=a41cde31c581e11d ''' def file_info(request): oshash = request.GET['oshash'] + f = models.MovieFile.objects.get(oshash=oshash) + response = f.json() + return render_to_json_response(response) ''' @@ -159,9 +237,20 @@ srt = def subtitles(request): oshash = request.GET['oshash'] language = request.GET.get('language', None) - if language: - return srt - return movie.subtitle_languages() + if requeset.method == 'POST': + user = request.user + sub = models.Subtitles.get_or_create(user, oshash, language) + sub.srt = request.POST['srt'] + sub.save() + else: + if language: + q = models.Subtitles.objects.filter(movie_file__oshash=oshash, language=language) + if q.count() > 0: + return HttpResponse(q[0].srt, content_type='text/x-srt') + response = {} + l = models.Subtitles.objects.filter(movie_file__oshash=oshash).values('language') + response['languages'] = [f['language'] for f in l] + return render_to_json_response(response) ''' GET list @@ -171,9 +260,27 @@ GET list } } ''' +@login_required def list_files(request): - files = {} - return dict(files=files) + response['files'] = {} + qs = models.UserFile.filter(user=request.user) + p = Paginator(qs, 1000) + for i in p.page_range: + page = p.page(i) + for f in page.object_list: + response['files'][f.movie_file.oshash] = {'path': f.path, 'size': f.movie_file.size} + return render_to_json_response(response) + +def find_files(request): + query = parse_query(request) + response['files'] = {} + qs = models.UserFile.filter(user=request.user).filter(movie_file__movie__id__in=quert['q']) + p = Paginator(qs, 1000) + for i in p.page_range: + page = p.page(i) + for f in page.object_list: + response['files'][f.movie_file.oshash] = {'path': f.path, 'size': f.movie_file.size} + return render_to_json_response(response) ''' POST add @@ -196,13 +303,36 @@ POST add "md5":.. } ''' -def add_file(request): +@login_required +def add_file(request, archive): oshash = request.POST['oshash'] + archive = models.Archive.objects.get(name=archive) + if archive.users.filter(user=request.user).count() == 1: + user_file = models.ArchiveFiles.get_or_create(request.user, oshash) + user_file.update(request.POST) + response = {'status': 200} + else: + response = {'status': 404} + return render_to_json_response(response) ''' POST remove?oshash= ''' -def remove_file(request): +@login_required +def remove_file(request, archive): oshash = request.POST['oshash'] + archive = models.Archive.objects.get(name=archive) + models.UserFiles.objects.filter(movie_file__oshash=oshash, user=request.user).delete() + response = {'status': 200} + return render_to_json_response(response) + +''' +POST preferences/get?key= +POST preferences/set?key=&value +''' +@login_required +def preferences(request): + oshash = request.POST['oshash'] + return '' diff --git a/settings.py b/settings.py index 3625d002..a086da7d 100644 --- a/settings.py +++ b/settings.py @@ -1,4 +1,10 @@ -# Django settings for oxdata project. +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +# Django settings for oxdb project. +import os +from os.path import join + +PROJECT_PATH = os.path.normpath(os.path.dirname(__file__)) DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -21,7 +27,7 @@ DATABASE_PORT = '' # Set to empty string for default. Not used with # although not all choices may be available on all operating systems. # If running in a Windows environment this must be set to the same as your # system time zone. -TIME_ZONE = 'America/Chicago' +TIME_ZONE = 'Europe/Berlin' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html @@ -35,7 +41,8 @@ USE_I18N = True # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = '' +MEDIA_ROOT = join(PROJECT_PATH, 'media') +STATIC_ROOT = join(PROJECT_PATH, 'static') # 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). @@ -45,7 +52,7 @@ MEDIA_URL = '' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. # Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = '/media/' +ADMIN_MEDIA_PREFIX = '/admin/media/' # Make this unique, and don't share it with anybody. SECRET_KEY = '3fh^twg4!7*xcise#3d5%ty+^-#9+*f0innkjcco+y0dag_nr-' @@ -66,9 +73,7 @@ MIDDLEWARE_CLASSES = ( ROOT_URLCONF = 'oxdb.urls' TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. + join(PROJECT_PATH, 'templates'), ) INSTALLED_APPS = ( @@ -76,5 +81,20 @@ INSTALLED_APPS = ( 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', + 'django.contrib.admin', + 'django.contrib.humanize', + 'oxdb.backend', ) + +try: + import socket + # hostname = socket.gethostname().replace('.','_') + # exec "from host_settings.%s import *" % hostname + local_settings_module = socket.gethostname().split(".")[0] + if local_settings_module: + execfile(os.path.join(PROJECT_PATH, "host_settings", "%s.py" % local_settings_module)) +except ImportError, e: + raise e + + diff --git a/urls.py b/urls.py index c6aa40ce..2662985d 100644 --- a/urls.py +++ b/urls.py @@ -1,8 +1,11 @@ from django.conf.urls.defaults import * +from django.conf import settings + # Uncomment the next two lines to enable the admin: -# from django.contrib import admin -# admin.autodiscover() +from django.contrib import admin +admin.autodiscover() + urlpatterns = patterns('', # Example: @@ -13,5 +16,15 @@ urlpatterns = patterns('', # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: - # (r'^admin/(.*)', admin.site.root), + (r'^admin/(.*)', admin.site.root), ) + +if settings.DEBUG: + urlpatterns += patterns('', + (r'^media/(?P.*)$', 'django.views.static.serve', + {'document_root': settings.MEDIA_ROOT}), + (r'^static/(?P.*)$', 'django.views.static.serve', + {'document_root': settings.STATIC_ROOT}), + ) + +