Repository: lociii/jukebox Branch: master Commit: eb9d1795f022 Files: 63 Total size: 252.6 KB Directory structure: gitextract_yt7p7x97/ ├── .gitignore ├── CHANGES.txt ├── LICENSE.rst ├── MANIFEST.in ├── README.rst ├── bin/ │ └── jukebox ├── jukebox/ │ ├── __init__.py │ ├── jukebox.wsgi │ ├── jukebox_core/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── api.py │ │ ├── docs/ │ │ │ └── API.rst │ │ ├── forms.py │ │ ├── management/ │ │ │ ├── __init__.py │ │ │ └── commands/ │ │ │ ├── __init__.py │ │ │ ├── jukebox_index.py │ │ │ └── jukebox_setup.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_auto__del_field_album_Artist.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests/ │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ ├── api_albums.py │ │ │ ├── api_artists.py │ │ │ ├── api_favourites.py │ │ │ ├── api_genres.py │ │ │ ├── api_history.py │ │ │ ├── api_queue.py │ │ │ ├── api_songs.py │ │ │ └── api_years.py │ │ ├── urls.py │ │ ├── utils.py │ │ └── views.py │ ├── jukebox_web/ │ │ ├── __init__.py │ │ ├── locale/ │ │ │ ├── de/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ ├── django.po │ │ │ │ ├── djangojs.mo │ │ │ │ └── djangojs.po │ │ │ ├── en/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ ├── django.po │ │ │ │ ├── djangojs.mo │ │ │ │ └── djangojs.po │ │ │ ├── fr/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ ├── django.po │ │ │ │ ├── djangojs.mo │ │ │ │ └── djangojs.po │ │ │ └── pt_BR/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ ├── django.po │ │ │ ├── djangojs.mo │ │ │ └── djangojs.po │ │ ├── static/ │ │ │ ├── css/ │ │ │ │ └── music.css │ │ │ └── js/ │ │ │ └── music.js │ │ ├── templates/ │ │ │ ├── index.html │ │ │ └── login.html │ │ ├── urls.py │ │ └── views.py │ ├── manage.py │ ├── settings.py │ ├── settings_local.example.py │ └── urls.py ├── requirements.txt └── setup.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea/ dist/ jukebox.egg-info/ *.pyc *.sqlite *.pid jukebox/settings_local.py build/ *.sw* ================================================ FILE: CHANGES.txt ================================================ v0.1.0, 2011-11-17 -- Initial release v0.1.1, 2011-11-21 -- Fixed installer bugs, added personal history, added system tests for api v0.2.0, 2011-11-24 -- Language switch, sortable lists, google-like search operators, autoplay tries to play appropriate music, improved web interface v0.2.1, 2011-11-24 -- fixed issue with autoplay v0.3.0, 2011-11-28 -- Added jukebox_watch, added list of voters, minor improvements v0.3.1, 2012-09-11 -- Improved exception handling, added rss for current song, minor bug fixes v0.3.2, 2013-02-24 -- Update dependencies, fix authentication problems, switch from inotify to watchdog v0.3.3, 2013-02-28 -- Fix manifest v0.3.4, 2013-04-12 -- Fix to skip unauthorized sessions, updated wsgi handler v0.3.5, 2013-05-17 -- Update mutagen, fixed minor bugs v0.3.7, 2013-05-17 -- Fix buggy pypi package v0.4.0, 2013-06-25 -- Split jukebox in different packages, Strip artist from album data v0.4.1, 2013-06-25 -- Add missing wsgi file ================================================ FILE: LICENSE.rst ================================================ The MIT License Copyright (c) Jens Nistler, http://jensnistler.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MANIFEST.in ================================================ include CHANGES.txt LICENSE.rst README.rst recursive-include jukebox/jukebox_core/docs * recursive-include jukebox/jukebox_web/locale * recursive-include jukebox/jukebox_web/static * recursive-include jukebox/jukebox_web/templates * include jukebox/jukebox.wsgi ================================================ FILE: README.rst ================================================ Unmaintained ============ The big time of local mp3 libraries is over. Therefore this project has been abandoned and is currently unmaintained. Maybe have a look at https://qca.st/ which provides jukebox functionalities for music streaming services. Democratic Jukebox - your democratic music player ================================================== Ever wanted to listen to music with a larger group of people e.g. in your office? Who decides what to play? Make your music player democratic and give everyone the chance to promote their favourite song. Jukebox provides a web interface to search your music library and vote for songs to be played. The more votes a song gets, the sooner you will listen to it. At one point in your life your play queue might get empty. Don't worry, the jukebox will keep on playing. The playback system figures out who is online using the web interface or API and plays music to their liking. **Required system libraries** libshout3, libshout3-dev and python-dev are required to build the dependecy `python-shout `_. .. image:: http://static.jensnistler.de/jukebox.png :height: 404px :width: 872px :scale: 100% :alt: Democratic Jukebox - your democratic music player General ======== - Jukebox is available in english, german and brazilian portuguese - Jukebox uses Facebook, Twitter and Github for authentication (see `django-social-auth `_ for more authentication providers) Setup ================== Install `virtualenvwrapper `_ via `pip `_ if not alreay done: :: sudo pip install virtualenvwrapper Set up a project for jukebox: :: mkproject jukebox Install the jukebox in your fresh virtual environment: :: workon jukebox pip install jukebox Now it's time to configure the jukebox 1. Enter admin credentials and select authentication providers 2. Create the database 3. Index your music That's all :: jukebox jukebox_setup jukebox syncdb jukebox migrate jukebox jukebox_index --path=/path/to/library The django builtin development webserver will be sufficient to serve your office or party. Just start it up: :: jukebox runserver ip:port Now you're ready to put music in the queue. Playback ========= Currently there are two methods of playing the music chosen in jukebox. **shoutcast** Stream your music to a shoutcast compatible server :: pip install jukebox-shout See `jukebox_shout `_ for details and startup command. **mpg123** Play your music locally on the machine running the jukebox. :: pip install jukebox-mpg123 See `jukebox_mpg123 `_ for details and startup command. **Contribute!** Feel free to write additional playback modules and I'll add them to the list above. Live indexing =============== There is no need to update your index every time a new song is added to your library, just use the live indexer package. :: pip install jukebox-live-indexer See `jukebox_live_indexer `_ for details and startup command. API ============= jukebox_core provides a fully fledged REST API for authenticated users. See `API reference `_ Search filters =============== Jukebox supports google-like search filter. Available search fields: title, artist, album, genre, year. :: title:(love to dance) artist:bobby artist:(bobby baby) lucky title:(in ten years) genre:electronic License ======== MIT License. See `License `_ Contribute! ============ You want to contribute to this project? Just fork the repo and do this: :: mkproject jukebox git clone git@github.com:[username]/jukebox.git . git remote add upstream git://github.com/lociii/jukebox.git pip install -r requirements.txt cd jukebox Follow up configuring jukebox like described in Setup. Use ./manage.py instead of the jukebox command. You can now create a branch to make your actual changes and send a pull request. See `this article `_ for how to do this. Contributors ============= - Brazilian portuguese translation by `Luan Fonseca de Farias `_ - Bugfixes by `Peter Hoffmann `_ - Bugfixes by `Amir H. Hajizamani `_ - Bugfixes by `Gabriel Duman `_ - Bugfixes by `Steffen Zieger `_ - Bugfixes by `Jonas Baumann `_ - Bugfixes by `imithun `_ Release Notes ============== 0.1.0 - Initial release 0.1.1 - Fixed installer bugs - Added personal history - Added system tests for api 0.2.0 - Language switch - Sortable lists - Google-like search operators - Autoplay tries to play appropriate music - Improved web interface 0.2.1 - fixed issue with autoplay 0.3.0 - Added jukebox_watch - Added list of voters - Minor improvements 0.3.1 - Improved exception handling - Added rss for current song - Minor bug fixes 0.3.2 - Update dependencies - Fix authentication problems - Switch from inotify to watchdog 0.3.3 - Fix manifest 0.3.4 - Fix to skip unauthorized sessions - Updated wsgi handler 0.3.5 - Update mutagen (Thanks guys for removing old packages) - Fixed minor bugs (Thanks to `saz `_) 0.3.7 - Fix buggy pypi package 0.4.0 - Split jukebox in different packages - Strip artist from album data 0.4.1 - Add missing wsgi file ================================================ FILE: bin/jukebox ================================================ #!/usr/bin/env python import imp import sys try: path = imp.find_module('jukebox')[1] sys.path.append(path) except ImportError, e: sys.stderr.write("Can't find jukebox: " + str(e.message) + "\n") sys.exit(1) from django.core.management import execute_manager import settings if __name__ == '__main__': execute_manager(settings) ================================================ FILE: jukebox/__init__.py ================================================ ================================================ FILE: jukebox/jukebox.wsgi ================================================ import os import sys path = os.path.normpath(os.path.dirname(__file__)) if path not in sys.path: sys.path.append(path) os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jukebox.settings') from django.core.wsgi import get_wsgi_application application = get_wsgi_application() ================================================ FILE: jukebox/jukebox_core/__init__.py ================================================ ================================================ FILE: jukebox/jukebox_core/admin.py ================================================ # -*- coding: UTF-8 -*- from models import Artist, Genre, Album, Song, Queue, History, Favourite from django.contrib import admin class ArtistAdmin(admin.ModelAdmin): list_display = ('Name', ) search_fields = ['Name'] class GenreAdmin(admin.ModelAdmin): list_display = ('Name', ) class AlbumAdmin(admin.ModelAdmin): list_display = ('Title', ) search_fields = ['Title'] class SongAdmin(admin.ModelAdmin): list_display = ('Title', 'Artist', 'Year', 'Genre', ) search_fields = ['Title'] class QueueAdmin(admin.ModelAdmin): list_display = ('Song', 'Created', ) class HistoryAdmin(admin.ModelAdmin): list_display = ('Song', 'Created', ) class FavouriteAdmin(admin.ModelAdmin): list_display = ('Song', 'User', 'Created', ) search_fields = ['User__username'] admin.site.register(Artist, ArtistAdmin) admin.site.register(Genre, GenreAdmin) admin.site.register(Album, AlbumAdmin) admin.site.register(Song, SongAdmin) admin.site.register(Queue, QueueAdmin) admin.site.register(History, HistoryAdmin) admin.site.register(Favourite, FavouriteAdmin) ================================================ FILE: jukebox/jukebox_core/api.py ================================================ # -*- coding: UTF-8 -*- from django.core.paginator import Paginator, InvalidPage from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.db.models import Count, Min, Q from django.contrib.sessions.models import Session from django.utils import formats import os, re, time from datetime import datetime from signal import SIGABRT from django.contrib.auth.models import User from models import Song, Artist, Album, Genre, Queue, Favourite, History, Player class api_base: count = 30 user_id = None search_term = None search_title = None search_artist_name = None search_album_title = None filter_year = None filter_genre = None filter_album_id = None filter_artist_id = None order_by_field = None order_by_direction = None order_by_fields = [] order_by_directions = ["asc", "desc"] order_by_default = None def set_count(self, count): if count > 100: self.count = 100 elif count > 0: self.count = count def set_user_id(self, user_id): self.user_id = user_id def set_search_term(self, term): options = self.parseSearchString( ( "title", "artist", "album", "genre", "year", ), term ) for key, value in options.items(): if key == "title": self.set_search_title(value) elif key == "artist": self.set_search_artist_name(value) elif key == "album": self.set_search_album_title(value) elif key == "genre": try: genre = Genre.objects.all().filter(Name__iexact=value)[0:1].get() self.set_filter_genre(genre.id) except ObjectDoesNotExist: pass elif key == "year": self.set_filter_year(value) self.search_term = options["term"] def parseSearchString(self, keywords, term): values = {} for i in range(len(keywords)): do_continue = False keyword = keywords[i] value = None pos = term.find(keyword + ":") if pos != -1: value_start = pos + len(keyword) + 1 # no brackets, search for next whitespace if term[value_start:value_start + 1] != "(": value_end = term.find(" ", value_start) if value_end == -1: value_end = len(term) do_continue = True value = term[value_start:value_end] # search for next closing bracket but count opened ones else: i = value_start + 1 bracket_count = 1 while i < len(term): char = term[i:i+1] if char == "(": bracket_count+= 1 elif char == ")": bracket_count-= 1 if not bracket_count: value = term[value_start:i+1] continue i+= 1 if not value: value = term[value_start:len(term)] do_continue = True if value is not None: values[keyword] = value if do_continue: continue for key, value in values.items(): term = term.replace(key + ":" + value, "").strip() if value.startswith("("): values[key] = value[1:len(value)-1] values["term"] = re.sub("\s+", " ", term) return values def set_search_title(self, term): self.search_title = term def set_search_artist_name(self, term): self.search_artist_name = term def set_search_album_title(self, term): self.search_album_title = term def set_filter_year(self, term): self.filter_year = term def set_filter_genre(self, term): self.filter_genre = term def set_filter_album_id(self, term): self.filter_album_id = term def set_filter_artist_id(self, term): self.filter_artist_id = term def set_order_by(self, field, direction="asc"): if (not field in self.order_by_fields or not direction in self.order_by_directions): return self.order_by_field = field self.order_by_direction = direction def get_default_result(self, result_type, page): search = {} if self.search_title is not None: value = self.search_title if value.find(" ") != -1: value = "(" + value + ")" search["title"] = value if self.search_artist_name is not None: value = self.search_artist_name if value.find(" ") != -1: value = "(" + value + ")" search["artist"] = value if self.search_album_title is not None: value = self.search_album_title if value.find(" ") != -1: value = "(" + value + ")" search["album"] = value if self.filter_genre is not None: genre = Genre.objects.all().filter(id=self.filter_genre)[0:1].get() value = genre.Name if value.find(" ") != -1: value = "(" + value + ")" search["genre"] = value search["genre_id"] = genre.id if self.filter_year is not None: search["year"] = str(self.filter_year) if self.search_term is not None: search["term"] = self.search_term return { "type": result_type, "page": page, "hasNextPage": False, "itemList": [], "order": [], "search": search, } def result_add_queue_and_favourite(self, song, dataset): if not self.user_id is None: try: queue = Queue.objects.get(Song=song) for user in queue.User.all(): if user.id == self.user_id: dataset["queued"] = True break except ObjectDoesNotExist: pass try: user = User.objects.get(id=self.user_id) Favourite.objects.get(Song=song, User=user) dataset["favourite"] = True except ObjectDoesNotExist: pass return dataset def source_set_order(self, object_list): if not self.order_by_field is None: field_name = self.order_by_fields.get(self.order_by_field) if self.order_by_direction == "desc": field_name = "-" + field_name return object_list.order_by(field_name) elif not self.order_by_default is None: order = [] for key, value in self.order_by_default.items(): order.append(value) object_list = object_list.order_by(*order) return object_list def result_set_order(self, result): result["order"] = [] if not self.order_by_field is None: result["order"].append({ "field": self.order_by_field, "direction": self.order_by_direction, }) elif not self.order_by_default is None: for field, order in self.order_by_default.items(): result["order"].append({ "field": field, "direction": "desc" if order.startswith("-") else "asc", }) return result class songs(api_base): order_by_fields = { "title": "Title", "artist": "Artist__Name", "album": "Album__Title", "year": "Year", "genre": "Genre__Name", "length": "Length", } order_by_default = { "title": "Title", } def index(self, page=1): object_list = Song.objects.all() # searches if not self.search_term is None: object_list = object_list.filter( Q(Title__contains=self.search_term) | Q(Artist__Name__contains=self.search_term) | Q(Album__Title__contains=self.search_term) ) if not self.search_title is None: object_list = object_list.filter( Title__contains=self.search_title ) if not self.search_artist_name is None: object_list = object_list.filter( Artist__Name__contains=self.search_artist_name ) if not self.search_album_title is None: object_list = object_list.filter( Album__Title__contains=self.search_album_title ) # filters if not self.filter_year is None: object_list = object_list.filter( Year__exact=self.filter_year ) if not self.filter_genre is None: object_list = object_list.filter( Genre__exact=self.filter_genre ) if not self.filter_album_id is None: object_list = object_list.filter( Album__exact=self.filter_album_id ) if not self.filter_artist_id is None: object_list = object_list.filter( Artist__exact=self.filter_artist_id ) # order object_list = self.source_set_order(object_list) # prepare result result = self.get_default_result("songs", page) # get data paginator = Paginator(object_list, self.count) try: page_obj = paginator.page(page) except InvalidPage: return result result = self.result_set_order(result) result["hasNextPage"] = page_obj.has_next() for item in page_obj.object_list: dataset = { "id": item.id, "title": None, "artist": { "id": None, "name": None, }, "album": { "id": None, "title": None, }, "year": None, "genre": { "id": None, "name": None, }, "length": None, "queued": False, "favourite": False, } if not item.Title is None: dataset["title"] = item.Title if not item.Artist is None: dataset["artist"]["id"] = item.Artist.id dataset["artist"]["name"] = item.Artist.Name if not item.Album is None: dataset["album"]["id"] = item.Album.id dataset["album"]["title"] = item.Album.Title if not item.Year is None: dataset["year"] = item.Year if not item.Genre is None: dataset["genre"]["id"] = item.Genre.id dataset["genre"]["name"] = item.Genre.Name if not item.Length is None: dataset["length"] = item.Length dataset = self.result_add_queue_and_favourite(item, dataset) result["itemList"].append(dataset) return result def getNextSong(self): # commit transaction to force fresh queryset result try: transaction.enter_transaction_management() transaction.commit() except BaseException: pass try: data = Queue.objects.all() data = data.annotate(VoteCount=Count("User")) data = data.annotate(MinCreated=Min("Created")) data = data.order_by("-VoteCount", "MinCreated")[0:1].get() self.addToHistory(data.Song, data.User) song_instance = data.Song data.delete() except ObjectDoesNotExist: try: song_instance = self.getRandomSongByPreferences() self.addToHistory(song_instance, None) except ObjectDoesNotExist: song_instance = Song.objects.order_by('?')[0:1].get() self.addToHistory(song_instance, None) # remove missing files if not os.path.exists(song_instance.Filename.encode('utf8')): Song.objects.all().filter(id=song_instance.id).delete() return self.getNextSong() return song_instance def getRandomSongByPreferences(self): artists = {} # get logged in users sessions = Session.objects.exclude( expire_date__lt=datetime.today() ) for session in sessions.all(): data = session.get_decoded() if not "_auth_user_id" in data: continue user_id = data["_auth_user_id"] # get newest favourites favourites = Favourite.objects.filter(User__id=user_id)[0:30] for favourite in favourites: if not favourite.Song.Artist.id in artists: artists[favourite.Song.Artist.id] = 0 artists[favourite.Song.Artist.id]+= 1 # get last voted songs votes = History.objects.filter(User__id=user_id)[0:30] for vote in votes: if not vote.Song.Artist.id in artists: artists[vote.Song.Artist.id] = 0 artists[vote.Song.Artist.id]+= 1 # nothing played and no favourites if not len(artists): raise ObjectDoesNotExist # calculate top artists from operator import itemgetter sorted_artists = sorted( artists.iteritems(), key=itemgetter(1), reverse=True )[0:30] artists = [] for key in range(len(sorted_artists)): artists.append(sorted_artists[key][0]) # get the 50 last played songs history = History.objects.all()[0:50] last_played = [] for item in history: last_played.append(item.Song.id) # find a song not played recently song_instance = Song.objects.exclude( id__in=last_played ).filter( Artist__id__in=artists ).order_by('?')[0:1].get() return song_instance def addToHistory(self, song_instance, user_list): history_instance = History( Song=song_instance ) history_instance.save() if user_list is not None and user_list.count() > 0: for user_instance in user_list.all(): history_instance.User.add(user_instance) def skipCurrentSong(self): players = Player.objects.all() for player in players: try: os.kill(player.Pid, SIGABRT) except OSError: player.delete() class history(api_base): order_by_fields = { "title": "Song__Title", "artist": "Song__Artist__Name", "album": "Song__Album__Title", "year": "Song__Year", "genre": "Song__Genre__Name", "created": "Created", } order_by_default = { "created": "-Created", } def index(self, page=1): object_list = History.objects.all() object_list = self.source_set_order(object_list) result = self.build_result(object_list, page) result = self.result_set_order(result) return result def build_result(self, object_list, page): # prepare result result = self.get_default_result("history", page) # get data paginator = Paginator(object_list, self.count) try: page_obj = paginator.page(page) except InvalidPage: return result result = self.result_set_order(result) result["hasNextPage"] = page_obj.has_next() for item in page_obj.object_list: dataset = { "id": item.Song.id, "title": None, "artist": { "id": None, "name": None, }, "album": { "id": None, "title": None, }, "year": None, "genre": { "id": None, "name": None, }, "queued": False, "favourite": False, "created": formats.date_format( item.Created, "DATETIME_FORMAT" ), "votes": item.User.count(), "users": [], } if not item.Song.Title is None: dataset["title"] = item.Song.Title if not item.Song.Artist is None: dataset["artist"]["id"] = item.Song.Artist.id dataset["artist"]["name"] = item.Song.Artist.Name if not item.Song.Album is None: dataset["album"]["id"] = item.Song.Album.id dataset["album"]["title"] = item.Song.Album.Title if not item.Song.Year is None: dataset["year"] = item.Song.Year if not item.Song.Genre is None: dataset["genre"]["id"] = item.Song.Genre.id dataset["genre"]["name"] = item.Song.Genre.Name if not item.User.count() == 0: for user in item.User.all(): dataset["users"].append({ "id": user.id, "name": user.get_full_name() }) dataset = self.result_add_queue_and_favourite(item.Song, dataset) result["itemList"].append(dataset) return result def getCurrent(self): item = History.objects.all()[0:1].get() createdTimestamp = time.mktime(item.Created.timetuple()) dataset = { "id": item.Song.id, "title": None, "artist": { "id": None, "name": None, }, "album": { "id": None, "title": None, }, "year": None, "genre": { "id": None, "name": None, }, "queued": False, "favourite": False, "created": formats.date_format( item.Created, "DATETIME_FORMAT" ), "votes": item.User.count(), "users": [], "remaining": createdTimestamp + item.Song.Length - int(time.time()) } if not item.Song.Title is None: dataset["title"] = item.Song.Title if not item.Song.Artist is None: dataset["artist"]["id"] = item.Song.Artist.id dataset["artist"]["name"] = item.Song.Artist.Name if not item.Song.Album is None: dataset["album"]["id"] = item.Song.Album.id dataset["album"]["title"] = item.Song.Album.Title if not item.Song.Year is None: dataset["year"] = item.Song.Year if not item.Song.Genre is None: dataset["genre"]["id"] = item.Song.Genre.id dataset["genre"]["name"] = item.Song.Genre.Name return dataset class history_my(history): order_by_fields = { "title": "Song__Title", "artist": "Song__Artist__Name", "album": "Song__Album__Title", "year": "Song__Year", "genre": "Song__Genre__Name", "created": "Created", } order_by_default = { "created": "-Created", } def index(self, page=1): object_list = History.objects.all().filter(User__id=self.user_id) object_list = self.source_set_order(object_list) result = self.build_result(object_list, page) result = self.result_set_order(result) result["type"] = "history/my" return result class queue(api_base): order_by_fields = { "title": "Song__Title", "artist": "Song__Artist__Name", "album": "Song__Album__Title", "year": "Song__Year", "genre": "Song__Genre__Name", "created": "Created", "votes": "VoteCount", } order_by_default = { "votes": "-VoteCount", "created": "MinCreated", } def index(self, page=1): object_list = Queue.objects.all() object_list = object_list.annotate(VoteCount=Count("User")) object_list = object_list.annotate(MinCreated=Min("Created")) object_list = self.source_set_order(object_list) # prepare result result = self.get_default_result("queue", page) # get data paginator = Paginator(object_list, self.count) try: page_obj = paginator.page(page) except InvalidPage: return result result = self.result_set_order(result) result["hasNextPage"] = page_obj.has_next() for item in page_obj.object_list: result["itemList"].append(self.get(item.Song.id)) return result def get(self, song_id): song = Song.objects.get(id=song_id) item = Queue.objects.get(Song=song) result = { "id": item.Song.id, "title": None, "artist": { "id": None, "name": None, }, "album": { "id": None, "title": None, }, "year": None, "genre": { "id": None, "name": None, }, "queued": False, "favourite": False, "created": formats.date_format(item.Created, "DATETIME_FORMAT"), "votes": item.User.count(), "users": [], } if not item.Song.Title is None: result["title"] = item.Song.Title if not item.Song.Artist is None: result["artist"]["id"] = item.Song.Artist.id result["artist"]["name"] = item.Song.Artist.Name if not item.Song.Album is None: result["album"]["id"] = item.Song.Album.id result["album"]["title"] = item.Song.Album.Title if not item.Song.Year is None: result["year"] = item.Song.Year if not item.Song.Genre is None: result["genre"]["id"] = item.Song.Genre.id result["genre"]["name"] = item.Song.Genre.Name if not item.User.count() == 0: for user in item.User.all(): result["users"].append({"id": user.id, "name": user.get_full_name()}) result = self.result_add_queue_and_favourite(item.Song, result) return result def add(self, song_id): song = Song.objects.get(id=song_id) user = User.objects.get(id=self.user_id) try: queue = Queue.objects.get(Song=song) except ObjectDoesNotExist: queue = Queue( Song=song ) queue.save() queue.User.add(user) return song_id def remove(self, song_id): song = Song.objects.get(id=song_id) user = User.objects.get(id=self.user_id) queue = Queue.objects.get(Song=song) queue.User.remove(user) vote_count = queue.User.count() if not queue.User.count(): queue.delete() return { "id": song_id, "count": vote_count, } class favourites(api_base): order_by_fields = { "title": "Song__Title", "artist": "Song__Artist__Name", "album": "Song__Album__Title", "year": "Song__Year", "genre": "Song__Genre__Name", "created": "Created", } order_by_default = { "created": "-Created", } def index(self, page=1): object_list = Favourite.objects.all().filter(User__id=self.user_id) object_list = self.source_set_order(object_list) # prepare result result = self.get_default_result("favourites", page) # get data paginator = Paginator(object_list, self.count) try: page_obj = paginator.page(page) except InvalidPage: return result result = self.result_set_order(result) result["hasNextPage"] = page_obj.has_next() for item in page_obj.object_list: result["itemList"].append(self.get(item.Song.id)) return result def get(self, song_id): song = Song.objects.get(id=song_id) item = Favourite.objects.get(Song=song,User__id=self.user_id) result = { "id": item.Song.id, "title": None, "artist": { "id": None, "name": None, }, "album": { "id": None, "title": None, }, "year": None, "genre": { "id": None, "name": None, }, "queued": False, "favourite": False, "created": formats.date_format(item.Created, "DATETIME_FORMAT"), } if not item.Song.Title is None: result["title"] = item.Song.Title if not item.Song.Artist is None: result["artist"]["id"] = item.Song.Artist.id result["artist"]["name"] = item.Song.Artist.Name if not item.Song.Album is None: result["album"]["id"] = item.Song.Album.id result["album"]["title"] = item.Song.Album.Title if not item.Song.Year is None: result["year"] = item.Song.Year if not item.Song.Genre is None: result["genre"]["id"] = item.Song.Genre.id result["genre"]["name"] = item.Song.Genre.Name result = self.result_add_queue_and_favourite(item.Song, result) return result def add(self, song_id): song = Song.objects.get(id=song_id) user = User.objects.get(id=self.user_id) favourite = Favourite( Song=song, User=user ) favourite.save() return song_id def remove(self, song_id): song = Song.objects.get(id=song_id) user = User.objects.get(id=self.user_id) Favourite.objects.get( Song=song, User=user ).delete() return { "id": song_id, } class artists(api_base): order_by_fields = { "artist": "Name", } order_by_default = { "artist": "Name", } def index(self, page=1): # prepare result result = self.get_default_result("artists", page) object_list = Artist.objects.all() object_list = self.source_set_order(object_list) # get data paginator = Paginator(object_list, self.count) try: page_obj = paginator.page(page) except InvalidPage: return result result = self.result_set_order(result) result["hasNextPage"] = page_obj.has_next() for item in page_obj.object_list: dataset = { "id": item.id, "artist": item.Name, } result["itemList"].append(dataset) return result class albums(api_base): order_by_fields = { "album": "Title", } order_by_default = { "album": "Title", } def index(self, page=1): # prepare result result = self.get_default_result("albums", page) object_list = Album.objects.all() object_list = self.source_set_order(object_list) # get data paginator = Paginator(object_list, self.count) try: page_obj = paginator.page(page) except InvalidPage: return result result = self.result_set_order(result) result["hasNextPage"] = page_obj.has_next() for item in page_obj.object_list: dataset = { "id": item.id, "album": item.Title, } result["itemList"].append(dataset) return result class genres(api_base): order_by_fields = { "genre": "Name", } order_by_default = { "genre": "Name", } def index(self, page=1): # prepare result result = self.get_default_result("genres", page) object_list = Genre.objects.all() object_list = self.source_set_order(object_list) # get data paginator = Paginator(object_list, self.count) try: page_obj = paginator.page(page) except InvalidPage: return result result = self.result_set_order(result) result["hasNextPage"] = page_obj.has_next() for item in page_obj.object_list: dataset = { "id": item.id, "genre": item.Name, } result["itemList"].append(dataset) return result class years(api_base): order_by_fields = { "year": "Year", } order_by_default = { "year": "Year" } def index(self, page=1): # prepare result result = self.get_default_result("years", page) object_list = Song.objects.values("Year").distinct() object_list = object_list.exclude(Year=None).exclude(Year=0) object_list = self.source_set_order(object_list) # get data paginator = Paginator(object_list, self.count) try: page_obj = paginator.page(page) except InvalidPage: return result result = self.result_set_order(result) result["hasNextPage"] = page_obj.has_next() for item in page_obj.object_list: dataset = { "year": item["Year"], } result["itemList"].append(dataset) return result class players(api_base): def add(self, pid): player = Player( Pid=pid ) player.save() return player.id def remove(self, pid): Player.objects.get(Pid=pid).delete() return { "pid": pid, } ================================================ FILE: jukebox/jukebox_core/docs/API.rst ================================================ API ===== Jukebox core provides a REST API for authenticated users to control the jukebox. Please register a Django User in the admin interface and use HTTP basic authentication for external API access. Alternatively the API is accessible for authenticated users sending a session key. Tests ====== The API is completely unit tested. See jukebox_core/tests GET methods ============ :: /api/v1/songs /api/v1/artists /api/v1/albums /api/v1/genres /api/v1/years /api/v1/history /api/v1/history/my /api/v1/queue /api/v1/queue/[song_id] /api/v1/favourites /api/v1/favourites/[song_id] /api/v1/ping *Sort parameters for list functions* - order_by (field to order by, see below) - order_direction ("asc" or "desc", defaults to "asc") **/api/v1/songs** List songs *Available options* - count (item count to be returned) - page (page to be returned) - search_term (search in song title, artist name and album title) - search_title (search in song title) - search_artist (search in artist name) - search_album (search in album title) - filter_artist_id (filter by artist id) - filter_album_id (filter by album id) - filter_genre (filter by genre id) - filter_year (filter by release year) *Available sort options* - title (default, asc) - artist - album - year - genre - length **/api/v1/artists** List artists *Available sort options* - artist (default, asc) **/api/v1/albums** List albums *Available sort options* - album (default, asc) **/api/v1/genres** List genres *Available sort options* - genre (default, asc) **/api/v1/years** List years *Available sort options* - year (default, asc) **/api/v1/history** List all played songs *Available sort options* - title - artist - album - year - genre - created (default, desc) **/api/v1/history/my** List all played songs the authenticated user voted for See /api/v1/history for details **/api/v1/queue** List songs in play queue sorted by vote count and first vote *Available sort options* - title - artist - album - year - genre - created (default, asc) - votes (default, desc) **/api/v1/queue/[song_id]** Get single play queue entry **/api/v1/favourites** *Available sort options* - title (default, asc) - artist - album - year - genre - created **/api/v1/favourites/[song_id]** Get single favourite list entry **/api/v1/ping** Ping the api for session keepalive POST methods ============ :: /api/v1/queue /api/v1/favourites **/api/v1/queue** Vote for song, add to queue if not yet in *Required post parameters* - id (id of song to be added) **/api/v1/favourites** Add song to favourite list *Required post parameters* - id (id of song to be added) DELETE methods =============== :: /api/v1/queue/[song_id] /api/v1/favourites/[song_id] **/api/v1/queue/[song_id]** Revoke vote for song, remove from queue if no more votes left **/api/v1/favourites/[song_id]** Remove song from favourite list ================================================ FILE: jukebox/jukebox_core/forms.py ================================================ # -*- coding: UTF-8 -*- from django import forms class IdForm(forms.Form): id = forms.IntegerField( required=True ) class SongsForm(forms.Form): count = forms.IntegerField( required=False ) page = forms.IntegerField( required=False ) search_term = forms.CharField( required=False ) search_title = forms.CharField( required=False ) search_artist = forms.CharField( required=False ) search_album = forms.CharField( required=False ) filter_year = forms.IntegerField( required=False ) filter_genre = forms.IntegerField( required=False ) filter_album_id = forms.IntegerField( required=False ) filter_artist_id = forms.IntegerField( required=False ) order_by = forms.CharField( max_length=6, help_text="'title', 'artist', 'album', 'year', 'genre'", required=False ) order_direction = forms.CharField( max_length=4, help_text="'asc', 'desc'", required=False ) class ArtistsForm(forms.Form): count = forms.IntegerField( required=False ) page = forms.IntegerField( required=False ) order_by = forms.CharField( max_length=6, help_text="'artist'", required=False ) order_direction = forms.CharField( max_length=4, help_text="'asc', 'desc'", required=False ) class AlbumsForm(forms.Form): count = forms.IntegerField( required=False ) page = forms.IntegerField( required=False ) order_by = forms.CharField( max_length=6, help_text="'album', 'artist'", required=False ) order_direction = forms.CharField( max_length=4, help_text="'asc', 'desc'", required=False ) class GenresForm(forms.Form): count = forms.IntegerField( required=False ) page = forms.IntegerField( required=False ) order_by = forms.CharField( max_length=5, help_text="'genre'", required=False ) order_direction = forms.CharField( max_length=4, help_text="'asc', 'desc'", required=False ) class YearsForm(forms.Form): count = forms.IntegerField( required=False ) page = forms.IntegerField( required=False ) order_by = forms.CharField( max_length=4, help_text="'year'", required=False ) order_direction = forms.CharField( max_length=4, help_text="'asc', 'desc'", required=False ) class HistoryForm(forms.Form): count = forms.IntegerField( required=False ) page = forms.IntegerField( required=False ) order_by = forms.CharField( max_length=7, help_text="'title', 'artist', 'album', 'year', 'genre', 'created'", required=False ) order_direction = forms.CharField( max_length=4, help_text="'asc', 'desc'", required=False ) class FavouritesForm(forms.Form): count = forms.IntegerField( required=False ) page = forms.IntegerField( required=False ) order_by = forms.CharField( max_length=7, help_text="'title', 'artist', 'album', 'year', 'genre', 'created'", required=False ) order_direction = forms.CharField( max_length=4, help_text="'asc', 'desc'", required=False ) class QueueForm(forms.Form): count = forms.IntegerField( required=False ) page = forms.IntegerField( required=False ) order_by = forms.CharField( max_length=7, help_text="'title', 'artist', 'album', 'year', \ 'genre', 'created', 'votes'", required=False ) order_direction = forms.CharField( max_length=4, help_text="'asc', 'desc'", required=False ) ================================================ FILE: jukebox/jukebox_core/management/__init__.py ================================================ ================================================ FILE: jukebox/jukebox_core/management/commands/__init__.py ================================================ ================================================ FILE: jukebox/jukebox_core/management/commands/jukebox_index.py ================================================ # -*- coding: UTF-8 -*- from django.core.management.base import BaseCommand from optparse import make_option import os from jukebox.jukebox_core.utils import FileIndexer class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option("--path", action="store", dest="path", help="Music library path to scan"), ) def handle(self, *args, **options): if options["path"] is None: print "Required arguments: path" return if not os.path.exists(options["path"]): print "Path does not exist" return print "Indexing music in " + options["path"] print "This may take a while" self.index(options["path"], int(options["verbosity"])) def index(self, path, verbosity): if not path.endswith("/"): path += "/" indexer = FileIndexer() listing = os.listdir(path) for filename in listing: filename = path + filename if os.path.isdir(filename): self.index(filename + "/", verbosity) elif filename.endswith(".mp3"): if verbosity >= 2: print "Indexing file " + filename indexer.index(filename) ================================================ FILE: jukebox/jukebox_core/management/commands/jukebox_setup.py ================================================ # -*- coding: UTF-8 -*- from django.core.management.base import BaseCommand from django.conf import settings class Command(BaseCommand): def handle(self, *args, **options): print "----------------------------------------------" print "----- Welcome to the jukebox setup -----" print "----------------------------------------------" print "" print "Page administrator" print "----------------------------------------------" print "" admin_user = raw_input("\tName: ") admin_email = raw_input("\tE-mail: ") print "" # get authentication methods authentication = self.setAuthentication() while not authentication: authentication = self.setAuthentication() self.setup(admin_user, admin_email, authentication) def setAuthentication(self): print "Please select your authentication methods" print "----------------------------------------------" print "Available providers: Facebook, Twitter, Github" print "" print "Facebook" print "\tFacebook authentication requires setup of a Facebook app on "\ "http://developers.facebook.com/setup/" facebook = self.readAppData("Facebook") print "" print "Twitter" print "\tTwitter authentication requires setup of a Twitter app on "\ "https://dev.twitter.com/apps/new" twitter = self.readAppData("Twitter") print "" print "Github" print "\tGithub authentication requires setup of a Github app on "\ "https://github.com/settings/applications/new" github = self.readAppData("Github") print "" if facebook is None and twitter is None and github is None: print "Are your kidding me? Why didn't you select a provider?" print "I won't let you go until you select at least one of them." print "" return False return { "facebook": facebook, "twitter": twitter, "github": github, } def readAppData(self, name): type = raw_input( "\tUse " + name + " for authentication? [y/n] " ).strip().lower() while type != "n" and type != "y": type = raw_input( "\tInvalid answer, please enter 'y' for yes or 'n' for no: " ).strip().lower() if type == "y": id = "" while id.strip() == "": id = raw_input("\t" + name + " app id: ") if id.strip() == "": print "\t\tInvald app id" secret = "" while secret.strip() == "": secret = raw_input("\t" + name + " app secret: ") if secret.strip() == "": print "\t\tInvald app id" return { "id": id, "secret": secret, } return None def setup(self, admin_user, admin_email, authentication): setup = open(settings.BASE_DIR + "/settings_local.example.py").read() setup = setup.replace("[admin_user]", admin_user) setup = setup.replace("[admin_email]", admin_email) auth_backends = [] auth_backends_enabled = [] if authentication["facebook"] is not None: auth_backends.append( "\"social_auth.backends.facebook.FacebookBackend\"," ) auth_backends_enabled.append("\"facebook\",") if authentication["twitter"] is not None: auth_backends.append( "\"social_auth.backends.twitter.TwitterBackend\"," ) auth_backends_enabled.append("\"twitter\",") if authentication["github"] is not None: auth_backends.append( "\"social_auth.backends.contrib.github.GithubBackend\"," ) auth_backends_enabled.append("\"github\",") setup = setup.replace( "[auth_backends]", "\n ".join(auth_backends) ) setup = setup.replace( "[auth_backends_enabled]", " ".join(auth_backends_enabled) ) auth_data = "" for key, value in authentication.items(): if value is None: continue if key == 'twitter': auth_data += "TWITTER_CONSUMER_KEY = \"" + \ value["id"] + "\"\n" auth_data += "TWITTER_CONSUMER_SECRET = \"" + \ value["secret"] + "\"\n" else: auth_data += key.upper() + "_APP_ID = \"" + \ value["id"] + "\"\n" auth_data += key.upper() + "_API_SECRET = \"" + \ value["secret"] + "\"\n" auth_data += "\n" setup = setup.replace("[auth_data]", auth_data) f = open(settings.JUKEBOX_STORAGE_PATH + "/settings_local.py", "w+") f.write(setup) f.close() print "Setup finished" print "----------------------------------------------" ================================================ FILE: jukebox/jukebox_core/migrations/0001_initial.py ================================================ # -*- coding: utf-8 -*- import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): # Adding model 'Artist' db.create_table('jukebox_core_artist', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('Name', self.gf('django.db.models.fields.CharField')(max_length=200)), )) db.send_create_signal('jukebox_core', ['Artist']) # Adding model 'Genre' db.create_table('jukebox_core_genre', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('Name', self.gf('django.db.models.fields.CharField')(max_length=200)), )) db.send_create_signal('jukebox_core', ['Genre']) # Adding model 'Album' db.create_table('jukebox_core_album', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('Title', self.gf('django.db.models.fields.CharField')(max_length=200)), ('Artist', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jukebox_core.Artist'])), )) db.send_create_signal('jukebox_core', ['Album']) # Adding model 'Song' db.create_table('jukebox_core_song', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('Artist', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jukebox_core.Artist'])), ('Album', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jukebox_core.Album'], null=True)), ('Genre', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jukebox_core.Genre'], null=True)), ('Title', self.gf('django.db.models.fields.CharField')(max_length=200)), ('Year', self.gf('django.db.models.fields.IntegerField')(null=True)), ('Length', self.gf('django.db.models.fields.IntegerField')()), ('Filename', self.gf('django.db.models.fields.CharField')(max_length=1000)), )) db.send_create_signal('jukebox_core', ['Song']) # Adding model 'Queue' db.create_table('jukebox_core_queue', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('Song', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jukebox_core.Song'], unique=True)), ('Created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), )) db.send_create_signal('jukebox_core', ['Queue']) # Adding M2M table for field User on 'Queue' db.create_table('jukebox_core_queue_User', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), ('queue', models.ForeignKey(orm['jukebox_core.queue'], null=False)), ('user', models.ForeignKey(orm['auth.user'], null=False)) )) db.create_unique('jukebox_core_queue_User', ['queue_id', 'user_id']) # Adding model 'Favourite' db.create_table('jukebox_core_favourite', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('Song', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jukebox_core.Song'])), ('User', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), ('Created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), )) db.send_create_signal('jukebox_core', ['Favourite']) # Adding unique constraint on 'Favourite', fields ['Song', 'User'] db.create_unique('jukebox_core_favourite', ['Song_id', 'User_id']) # Adding model 'History' db.create_table('jukebox_core_history', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('Song', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['jukebox_core.Song'])), ('Created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), )) db.send_create_signal('jukebox_core', ['History']) # Adding M2M table for field User on 'History' db.create_table('jukebox_core_history_User', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), ('history', models.ForeignKey(orm['jukebox_core.history'], null=False)), ('user', models.ForeignKey(orm['auth.user'], null=False)) )) db.create_unique('jukebox_core_history_User', ['history_id', 'user_id']) # Adding model 'Player' db.create_table('jukebox_core_player', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('Pid', self.gf('django.db.models.fields.IntegerField')()), )) db.send_create_signal('jukebox_core', ['Player']) def backwards(self, orm): # Removing unique constraint on 'Favourite', fields ['Song', 'User'] db.delete_unique('jukebox_core_favourite', ['Song_id', 'User_id']) # Deleting model 'Artist' db.delete_table('jukebox_core_artist') # Deleting model 'Genre' db.delete_table('jukebox_core_genre') # Deleting model 'Album' db.delete_table('jukebox_core_album') # Deleting model 'Song' db.delete_table('jukebox_core_song') # Deleting model 'Queue' db.delete_table('jukebox_core_queue') # Removing M2M table for field User on 'Queue' db.delete_table('jukebox_core_queue_User') # Deleting model 'Favourite' db.delete_table('jukebox_core_favourite') # Deleting model 'History' db.delete_table('jukebox_core_history') # Removing M2M table for field User on 'History' db.delete_table('jukebox_core_history_User') # Deleting model 'Player' db.delete_table('jukebox_core_player') models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, 'auth.permission': { 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, 'auth.user': { 'Meta': {'object_name': 'User'}, 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, 'contenttypes.contenttype': { 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, 'jukebox_core.album': { 'Artist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Artist']"}), 'Meta': {'ordering': "['Title']", 'object_name': 'Album'}, 'Title': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.artist': { 'Meta': {'ordering': "['Name']", 'object_name': 'Artist'}, 'Name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.favourite': { 'Created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'Meta': {'ordering': "['-Created']", 'unique_together': "(('Song', 'User'),)", 'object_name': 'Favourite'}, 'Song': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Song']"}), 'User': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.genre': { 'Meta': {'ordering': "['Name']", 'object_name': 'Genre'}, 'Name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.history': { 'Created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'Meta': {'ordering': "['-Created']", 'object_name': 'History'}, 'Song': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Song']"}), 'User': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'null': 'True', 'symmetrical': 'False'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.player': { 'Meta': {'object_name': 'Player'}, 'Pid': ('django.db.models.fields.IntegerField', [], {}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.queue': { 'Created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'Meta': {'object_name': 'Queue'}, 'Song': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Song']", 'unique': 'True'}), 'User': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.song': { 'Album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Album']", 'null': 'True'}), 'Artist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Artist']"}), 'Filename': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), 'Genre': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Genre']", 'null': 'True'}), 'Length': ('django.db.models.fields.IntegerField', [], {}), 'Meta': {'ordering': "['Title', 'Artist', 'Album']", 'object_name': 'Song'}, 'Title': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'Year': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) } } complete_apps = ['jukebox_core'] ================================================ FILE: jukebox/jukebox_core/migrations/0002_auto__del_field_album_Artist.py ================================================ # -*- coding: utf-8 -*- import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): # Deleting field 'Album.Artist' db.delete_column('jukebox_core_album', 'Artist_id') def backwards(self, orm): # User chose to not deal with backwards NULL issues for 'Album.Artist' raise RuntimeError("Cannot reverse this migration. 'Album.Artist' and its values cannot be restored.") models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, 'auth.permission': { 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, 'auth.user': { 'Meta': {'object_name': 'User'}, 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, 'contenttypes.contenttype': { 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, 'jukebox_core.album': { 'Meta': {'ordering': "['Title']", 'object_name': 'Album'}, 'Title': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.artist': { 'Meta': {'ordering': "['Name']", 'object_name': 'Artist'}, 'Name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.favourite': { 'Created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'Meta': {'ordering': "['-Created']", 'unique_together': "(('Song', 'User'),)", 'object_name': 'Favourite'}, 'Song': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Song']"}), 'User': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.genre': { 'Meta': {'ordering': "['Name']", 'object_name': 'Genre'}, 'Name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.history': { 'Created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'Meta': {'ordering': "['-Created']", 'object_name': 'History'}, 'Song': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Song']"}), 'User': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'null': 'True', 'symmetrical': 'False'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.player': { 'Meta': {'object_name': 'Player'}, 'Pid': ('django.db.models.fields.IntegerField', [], {}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.queue': { 'Created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'Meta': {'object_name': 'Queue'}, 'Song': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Song']", 'unique': 'True'}), 'User': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'jukebox_core.song': { 'Album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Album']", 'null': 'True'}), 'Artist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Artist']"}), 'Filename': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), 'Genre': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jukebox_core.Genre']", 'null': 'True'}), 'Length': ('django.db.models.fields.IntegerField', [], {}), 'Meta': {'ordering': "['Title', 'Artist', 'Album']", 'object_name': 'Song'}, 'Title': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'Year': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) } } complete_apps = ['jukebox_core'] ================================================ FILE: jukebox/jukebox_core/migrations/__init__.py ================================================ ================================================ FILE: jukebox/jukebox_core/models.py ================================================ # -*- coding: UTF-8 -*- from django.db import models from django.contrib.auth.models import User from django.contrib.syndication.views import Feed import time class Artist(models.Model): class Meta: ordering = ['Name'] def __unicode__(self): return "%s" % self.Name Name = models.CharField(max_length=200) class Genre(models.Model): class Meta: ordering = ['Name'] def __unicode__(self): return "%s" % self.Name Name = models.CharField(max_length=200) class Album(models.Model): class Meta: ordering = ['Title'] def __unicode__(self): return "%s" % self.Title Title = models.CharField(max_length=200) class Song(models.Model): class Meta: ordering = ['Title', 'Artist', 'Album'] def __unicode__(self): return "%s - %s" % (self.Artist.Name, self.Title) Artist = models.ForeignKey(Artist) Album = models.ForeignKey(Album, null=True) Genre = models.ForeignKey(Genre, null=True) Title = models.CharField(max_length=200) Year = models.IntegerField(null=True) Length = models.IntegerField() Filename = models.CharField(max_length=1000) class Queue(models.Model): Song = models.ForeignKey(Song, unique=True) User = models.ManyToManyField(User) Created = models.DateTimeField(auto_now_add=True) class Favourite(models.Model): class Meta: unique_together = ("Song", "User") ordering = ['-Created'] Song = models.ForeignKey(Song) User = models.ForeignKey(User) Created = models.DateTimeField(auto_now_add=True) class History(models.Model): class Meta: ordering = ['-Created'] Song = models.ForeignKey(Song) User = models.ManyToManyField(User, null=True) Created = models.DateTimeField(auto_now_add=True) class Player(models.Model): Pid = models.IntegerField() class QueueFeed(Feed): title = "Jukebox Queue Feed" link = "/queue/" description = "Top song in the queue" def items(self): return Queue.objects.all()[:1] def item_title(self, item): return item.Song.Title def item_description(self, item): return unicode(item.Song.Title) + " by " + \ unicode(item.Song.Artist) + " from " + \ unicode(item.Song.Album) def item_link(self, item): # Not sure what to do with url as there isn't any unque url for song return "/queue/#" + unicode(int(round(time.time() * 1000))) ================================================ FILE: jukebox/jukebox_core/tests/__init__.py ================================================ # -*- coding: UTF-8 -*- from .api_songs import * from .api_artists import * from .api_albums import * from .api_genres import * from .api_years import * from .api_favourites import * from .api_history import * from .api_queue import * ================================================ FILE: jukebox/jukebox_core/tests/api.py ================================================ # -*- coding: UTF-8 -*- from django.test import TestCase, Client from django.db import transaction import base64 from jukebox.jukebox_core.models import Artist, Album, Genre, Song from django.contrib.auth.models import User class ApiTestBase(TestCase): user = None username = "TestUser" email = "test@domain.org" password = "TestPassword" passwords = {} def setUp(self): transaction.rollback() # register test user and setup auth self.user = self.addUser(self.username, self.email, self.password) def httpGet(self, url, params={}, user=None): c = Client() return c.get(url, params, HTTP_AUTHORIZATION=self.getAuth(user)) def httpPost(self, url, params={}, user=None): c = Client() return c.post(url, params, HTTP_AUTHORIZATION=self.getAuth(user)) def httpDelete(self, url, params={}, user=None): c = Client() return c.delete(url, params, HTTP_AUTHORIZATION=self.getAuth(user)) def getAuth(self, user=None): if user is None: user = self.user username = user.username password = self.passwords[user.id] return "Basic %s" % base64.encodestring( '%s:%s' % (username, password) ).strip() def addArtist(self, name="TestArist"): artist = Artist( Name=name ) artist.save() return artist def addAlbum(self, title="TestTitle"): album = Album( Title=title ) album.save() return album def addGenre(self, name="TestGenre"): genre = Genre( Name=name ) genre.save() return genre def addSong( self, artist, album = None, genre = None, title="TestTitle", year=2000, length=100, filename="/path/to/test.mp3" ): # save a song song = Song( Artist=artist, Album=album, Genre=genre, Title=title, Year=year, Length=length, Filename=filename ) song.save() return song def addUser(self, username, email, password): user = User.objects.create_user(username, email, password) self.passwords[user.id] = password return user ================================================ FILE: jukebox/jukebox_core/tests/api_albums.py ================================================ # -*- coding: UTF-8 -*- import simplejson from jukebox.jukebox_core.tests.api import ApiTestBase class ApiAlbumsTest(ApiTestBase): def testIndexEmpty(self): result = simplejson.loads( self.httpGet( "/api/v1/albums" ).content ) self.assertEquals(len(result["itemList"]), 0) def testIndex(self): album = self.addAlbum() result = simplejson.loads( self.httpGet( "/api/v1/albums" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], album.id) def testIndexOrderByAlbum(self): album_a = self.addAlbum(title="A Title") album_b = self.addAlbum(title="B Title") result = simplejson.loads( self.httpGet( "/api/v1/albums?order_by=album" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], album_a.id) self.assertEquals(result["itemList"][1]["id"], album_b.id) result = simplejson.loads( self.httpGet( "/api/v1/albums?order_by=album&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], album_b.id) self.assertEquals(result["itemList"][1]["id"], album_a.id) def testCount(self): album_a = self.addAlbum("AAA") album_b = self.addAlbum("BBB") album_c = self.addAlbum("CCC") result = simplejson.loads( self.httpGet( "/api/v1/albums?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], album_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/albums?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["id"], album_a.id) self.assertEquals(result["itemList"][1]["id"], album_b.id) self.assertEquals(result["itemList"][2]["id"], album_c.id) self.assertFalse(result["hasNextPage"]) def testCountAndPage(self): album_a = self.addAlbum("AAA") album_b = self.addAlbum("BBB") album_c = self.addAlbum("CCC") result = simplejson.loads( self.httpGet( "/api/v1/albums?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], album_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/albums?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], album_b.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/albums?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], album_c.id) self.assertFalse(result["hasNextPage"]) ================================================ FILE: jukebox/jukebox_core/tests/api_artists.py ================================================ # -*- coding: UTF-8 -*- import simplejson from jukebox.jukebox_core.tests.api import ApiTestBase class ApiArtistsTest(ApiTestBase): def testIndexEmpty(self): result = simplejson.loads( self.httpGet( "/api/v1/artists" ).content ) self.assertEquals(len(result["itemList"]), 0) def testIndex(self): artist = self.addArtist() result = simplejson.loads( self.httpGet( "/api/v1/artists" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], artist.id) def testIndexOrderBy(self): artist_a = self.addArtist(name="A Name") artist_b = self.addArtist(name="B Name") result = simplejson.loads( self.httpGet( "/api/v1/artists?order_by=artist" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], artist_a.id) self.assertEquals(result["itemList"][1]["id"], artist_b.id) result = simplejson.loads( self.httpGet( "/api/v1/artists?order_by=artist&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], artist_b.id) self.assertEquals(result["itemList"][1]["id"], artist_a.id) def testCount(self): artist_a = self.addArtist() artist_b = self.addArtist() artist_c = self.addArtist() result = simplejson.loads( self.httpGet( "/api/v1/artists?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], artist_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/artists?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["id"], artist_a.id) self.assertEquals(result["itemList"][1]["id"], artist_b.id) self.assertEquals(result["itemList"][2]["id"], artist_c.id) self.assertFalse(result["hasNextPage"]) def testCountAndPage(self): artist_a = self.addArtist() artist_b = self.addArtist() artist_c = self.addArtist() result = simplejson.loads( self.httpGet( "/api/v1/artists?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], artist_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/artists?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], artist_b.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/artists?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], artist_c.id) self.assertFalse(result["hasNextPage"]) ================================================ FILE: jukebox/jukebox_core/tests/api_favourites.py ================================================ # -*- coding: UTF-8 -*- import simplejson from jukebox.jukebox_core.tests.api import ApiTestBase # ATTENTION: order tests # favourites are ordered by insertion date DESC per default class ApiFavouritesTest(ApiTestBase): def testIndexEmpty(self): result = simplejson.loads( self.httpGet( "/api/v1/favourites" ).content ) self.assertEquals(len(result["itemList"]), 0) def testAddAndIndex(self): # register second user user = self.addUser("TestUser2", "test2@domain.org", "TestPassword2") song = self.addSong(artist=self.addArtist()) # check that song is not a favourite result = simplejson.loads( self.httpGet( "/api/v1/songs" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) self.assertFalse(result["itemList"][0]["favourite"]) # add to favourites response = self.httpPost( "/api/v1/favourites", {"id": song.id} ) content = simplejson.loads( response.content ) self.assertEqual(response.status_code, 201) self.assertEqual(content["id"], song.id) # check favourites list result = simplejson.loads( self.httpGet( "/api/v1/favourites" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) # check that song is marked as favourite result = simplejson.loads( self.httpGet( "/api/v1/songs" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) self.assertTrue(result["itemList"][0]["favourite"]) # check favourites list of second user result = simplejson.loads( self.httpGet( "/api/v1/favourites", {}, user ).content ) self.assertEquals(len(result["itemList"]), 0) # check that song is not marked as favourite for second user result = simplejson.loads( self.httpGet( "/api/v1/songs", {}, user ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) self.assertFalse(result["itemList"][0]["favourite"]) def testDeleteAndIndex(self): song = self.addSong(artist=self.addArtist()) # add to favourites response = self.httpPost( "/api/v1/favourites", {"id": song.id} ) content = simplejson.loads( response.content ) self.assertEqual(response.status_code, 201) self.assertEqual(content["id"], song.id) # check favourites list result = simplejson.loads( self.httpGet( "/api/v1/favourites" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) # remove from favourites response = self.httpDelete( "/api/v1/favourites/" + str(song.id), ) content = simplejson.loads( response.content ) self.assertEqual(response.status_code, 200) self.assertEqual(content["id"], str(song.id)) # check favourites list result = simplejson.loads( self.httpGet( "/api/v1/favourites" ).content ) self.assertEquals(len(result["itemList"]), 0) def addFavourite(self, song): return self.httpPost( "/api/v1/favourites", {"id": song.id} ) def testIndexOrderByTitle(self): song_a = self.addSong(artist=self.addArtist(), title="A Title") song_b = self.addSong(artist=self.addArtist(), title="B Title") self.addFavourite(song_a) self.addFavourite(song_b) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=title" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=title&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByArtist(self): song_a = self.addSong(artist=self.addArtist(name="A Name")) song_b = self.addSong(artist=self.addArtist(name="B Name")) self.addFavourite(song_a) self.addFavourite(song_b) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=artist" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=artist&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByAlbum(self): album_a = self.addAlbum("A Title") album_b = self.addAlbum("B Title") song_a = self.addSong(artist=self.addArtist(), album=album_a) song_b = self.addSong(artist=self.addArtist(), album=album_b) self.addFavourite(song_a) self.addFavourite(song_b) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=album" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=album&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByYear(self): song_a = self.addSong(artist=self.addArtist(), year=2000) song_b = self.addSong(artist=self.addArtist(), year=2001) self.addFavourite(song_a) self.addFavourite(song_b) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=year" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=year&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByGenre(self): song_a = self.addSong( artist=self.addArtist(), genre=self.addGenre(name="A Genre") ) song_b = self.addSong( artist=self.addArtist(), genre=self.addGenre(name="B Genre") ) self.addFavourite(song_a) self.addFavourite(song_b) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=genre" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=genre&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByCreated(self): song_a = self.addSong(artist=self.addArtist()) song_b = self.addSong(artist=self.addArtist()) self.addFavourite(song_a) self.addFavourite(song_b) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=created" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/favourites?order_by=created&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testCount(self): song_a = self.addSong(artist=self.addArtist()) song_b = self.addSong(artist=self.addArtist()) song_c = self.addSong(artist=self.addArtist()) self.addFavourite(song_a) self.addFavourite(song_b) self.addFavourite(song_c) result = simplejson.loads( self.httpGet( "/api/v1/favourites?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/favourites?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) self.assertEquals(result["itemList"][2]["id"], song_a.id) self.assertFalse(result["hasNextPage"]) def testCountAndPage(self): song_a = self.addSong(artist=self.addArtist()) song_b = self.addSong(artist=self.addArtist()) song_c = self.addSong(artist=self.addArtist()) self.addFavourite(song_a) self.addFavourite(song_b) self.addFavourite(song_c) result = simplejson.loads( self.httpGet( "/api/v1/favourites?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/favourites?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/favourites?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertFalse(result["hasNextPage"]) ================================================ FILE: jukebox/jukebox_core/tests/api_genres.py ================================================ # -*- coding: UTF-8 -*- import simplejson from jukebox.jukebox_core.tests.api import ApiTestBase class ApiGenresTest(ApiTestBase): def testIndexEmpty(self): result = simplejson.loads( self.httpGet( "/api/v1/genres" ).content ) self.assertEquals(len(result["itemList"]), 0) def testIndex(self): genre = self.addGenre() result = simplejson.loads( self.httpGet( "/api/v1/genres" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], genre.id) def testIndexOrderBy(self): genre_a = self.addGenre(name="A Name") genre_b = self.addGenre(name="B Name") result = simplejson.loads( self.httpGet( "/api/v1/genres?order_by=genre" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], genre_a.id) self.assertEquals(result["itemList"][1]["id"], genre_b.id) result = simplejson.loads( self.httpGet( "/api/v1/genres?order_by=genre&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], genre_b.id) self.assertEquals(result["itemList"][1]["id"], genre_a.id) def testCount(self): genre_a = self.addGenre() genre_b = self.addGenre() genre_c = self.addGenre() result = simplejson.loads( self.httpGet( "/api/v1/genres?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], genre_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/genres?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["id"], genre_a.id) self.assertEquals(result["itemList"][1]["id"], genre_b.id) self.assertEquals(result["itemList"][2]["id"], genre_c.id) self.assertFalse(result["hasNextPage"]) def testCountAndPage(self): genre_a = self.addGenre() genre_b = self.addGenre() genre_c = self.addGenre() result = simplejson.loads( self.httpGet( "/api/v1/genres?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], genre_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/genres?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], genre_b.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/genres?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], genre_c.id) self.assertFalse(result["hasNextPage"]) ================================================ FILE: jukebox/jukebox_core/tests/api_history.py ================================================ # -*- coding: UTF-8 -*- import simplejson from jukebox.jukebox_core import api from jukebox.jukebox_core.tests.api import ApiTestBase # ATTENTION: order tests # favourites are ordered by insertion date DESC per default class ApiHistoryTest(ApiTestBase): def testIndexEmpty(self): result = simplejson.loads( self.httpGet( "/api/v1/history" ).content ) self.assertEquals(len(result["itemList"]), 0) def addSongToQueue(self, song, user=None): if user is None: user = self.user return self.httpPost( "/api/v1/queue", {"id": song.id}, user ) def getNextSong(self): songs_api = api.songs() return songs_api.getNextSong() def testAddAndIndex(self): song = self.addSong(artist=self.addArtist(), filename= __file__) # check that song is not in history result = simplejson.loads( self.httpGet( "/api/v1/history" ).content ) self.assertEquals(len(result["itemList"]), 0) # add to queue and play the song self.addSongToQueue(song) self.getNextSong() # check history result = simplejson.loads( self.httpGet( "/api/v1/history" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testAddAndIndexMy(self): # register second user user = self.addUser("TestUser2", "test2@domain.org", "TestPassword2") song_a = self.addSong(artist=self.addArtist(), filename=__file__) song_b = self.addSong(artist=self.addArtist(), filename=__file__) # check that song is not in history result = simplejson.loads( self.httpGet( "/api/v1/history/my" ).content ) self.assertEquals(len(result["itemList"]), 0) # add to queue and play the song self.addSongToQueue(song_a, user) self.addSongToQueue(song_b) self.getNextSong() # overall history contains the song result = simplejson.loads( self.httpGet( "/api/v1/history" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_a.id) # my history should still be empty result = simplejson.loads( self.httpGet( "/api/v1/history/my" ).content ) self.assertEquals(len(result["itemList"]), 0) # play my song self.getNextSong() # overall history contains both songs result = simplejson.loads( self.httpGet( "/api/v1/history" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) # check my history result = simplejson.loads( self.httpGet( "/api/v1/history/my" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_b.id) def testIndexOrderByTitle(self): song_a = self.addSong( artist=self.addArtist(), title="A Title", filename=__file__ ) song_b = self.addSong( artist=self.addArtist(), title="B Title", filename=__file__ ) self.addSongToQueue(song_a) self.addSongToQueue(song_b) self.getNextSong() self.getNextSong() result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=title" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=title" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=title&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=title&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByArtist(self): song_a = self.addSong( artist=self.addArtist("A Name"), filename=__file__ ) song_b = self.addSong( artist=self.addArtist("B Name"), filename=__file__ ) self.addSongToQueue(song_a) self.addSongToQueue(song_b) self.getNextSong() self.getNextSong() result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=artist" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=artist" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=artist&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=artist&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByAlbum(self): artist = self.addArtist() song_a = self.addSong( artist=artist, album=self.addAlbum(title="A Title"), filename=__file__ ) song_b = self.addSong( artist=artist, album=self.addAlbum(title="B Title"), filename=__file__ ) self.addSongToQueue(song_a) self.addSongToQueue(song_b) self.getNextSong() self.getNextSong() result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=album" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=album" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=album&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=album&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByYear(self): song_a = self.addSong( artist=self.addArtist(), filename=__file__ ) song_b = self.addSong( artist=self.addArtist(), year=2010, filename=__file__ ) self.addSongToQueue(song_a) self.addSongToQueue(song_b) self.getNextSong() self.getNextSong() result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=year" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=year" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=year&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=year&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByGenre(self): song_a = self.addSong( artist=self.addArtist(), genre=self.addGenre(name="A Name"), filename=__file__ ) song_b = self.addSong( artist=self.addArtist(), genre=self.addGenre(name="B Name"), filename=__file__ ) self.addSongToQueue(song_a) self.addSongToQueue(song_b) self.getNextSong() self.getNextSong() result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=genre" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=genre" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=genre&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=genre&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByCreated(self): song_a = self.addSong( artist=self.addArtist(), filename=__file__ ) song_b = self.addSong( artist=self.addArtist(), filename=__file__ ) self.addSongToQueue(song_a) self.addSongToQueue(song_b) self.getNextSong() self.getNextSong() result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=created" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=created" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/history?order_by=created&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) result = simplejson.loads( self.httpGet( "/api/v1/history/my?order_by=created&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testCount(self): song_a = self.addSong( artist=self.addArtist(), filename=__file__ ) song_b = self.addSong( artist=self.addArtist(), filename=__file__ ) song_c = self.addSong( artist=self.addArtist(), filename=__file__ ) self.addSongToQueue(song_a) self.addSongToQueue(song_b) self.addSongToQueue(song_c) self.getNextSong() self.getNextSong() self.getNextSong() result = simplejson.loads( self.httpGet( "/api/v1/history?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/history/my?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/history?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) self.assertEquals(result["itemList"][2]["id"], song_a.id) self.assertFalse(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/history/my?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) self.assertEquals(result["itemList"][2]["id"], song_a.id) self.assertFalse(result["hasNextPage"]) def testCountAndPage(self): song_a = self.addSong( artist=self.addArtist(), filename=__file__ ) song_b = self.addSong( artist=self.addArtist(), filename=__file__ ) song_c = self.addSong( artist=self.addArtist(), filename=__file__ ) self.addSongToQueue(song_a) self.addSongToQueue(song_b) self.addSongToQueue(song_c) self.getNextSong() self.getNextSong() self.getNextSong() result = simplejson.loads( self.httpGet( "/api/v1/history?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/history?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/history?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertFalse(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/history/my?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/history/my?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/history/my?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertFalse(result["hasNextPage"]) ================================================ FILE: jukebox/jukebox_core/tests/api_queue.py ================================================ # -*- coding: UTF-8 -*- import simplejson from jukebox.jukebox_core.tests.api import ApiTestBase class ApiQueueTest(ApiTestBase): def testIndexEmpty(self): result = simplejson.loads( self.httpGet( "/api/v1/queue" ).content ) self.assertEquals(len(result["itemList"]), 0) def testAddAndIndex(self): song = self.addSong(artist=self.addArtist()) # check that song is not in queue result = simplejson.loads( self.httpGet( "/api/v1/songs" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) self.assertFalse(result["itemList"][0]["queued"]) # add to queue response = self.httpPost( "/api/v1/queue", {"id": song.id} ) content = simplejson.loads( response.content ) self.assertEqual(response.status_code, 201) self.assertEqual(content["id"], song.id) # check queue result = simplejson.loads( self.httpGet( "/api/v1/queue" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) # check that song is marked as queued result = simplejson.loads( self.httpGet( "/api/v1/songs" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) self.assertTrue(result["itemList"][0]["queued"]) def testDeleteAndIndex(self): song = self.addSong(artist=self.addArtist()) # add to queue response = self.httpPost( "/api/v1/queue", {"id": song.id} ) content = simplejson.loads( response.content ) self.assertEqual(response.status_code, 201) self.assertEqual(content["id"], song.id) # check queue result = simplejson.loads( self.httpGet( "/api/v1/queue" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) # remove from queue response = self.httpDelete( "/api/v1/queue/" + str(song.id), ) content = simplejson.loads( response.content ) self.assertEqual(response.status_code, 200) self.assertEqual(content["id"], str(song.id)) # check queue result = simplejson.loads( self.httpGet( "/api/v1/queue" ).content ) self.assertEquals(len(result["itemList"]), 0) def addToQueue(self, song): return self.httpPost( "/api/v1/queue", {"id": song.id} ) def testIndexOrderByTitle(self): song_a = self.addSong(artist=self.addArtist(), title="A Title") song_b = self.addSong(artist=self.addArtist(), title="B Title") self.addToQueue(song_a) self.addToQueue(song_b) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=title" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=title&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByArtist(self): song_a = self.addSong(artist=self.addArtist(name="A Name")) song_b = self.addSong(artist=self.addArtist(name="B Name")) self.addToQueue(song_a) self.addToQueue(song_b) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=artist" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=artist&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByAlbum(self): album_a = self.addAlbum(title="A Title") album_b = self.addAlbum(title="B Title") song_a = self.addSong(artist=self.addArtist(), album=album_a) song_b = self.addSong(artist=self.addArtist(), album=album_b) self.addToQueue(song_a) self.addToQueue(song_b) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=album" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=album&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByYear(self): song_a = self.addSong(artist=self.addArtist(), year=2000) song_b = self.addSong(artist=self.addArtist(), year=2001) self.addToQueue(song_a) self.addToQueue(song_b) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=year" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=year&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByGenre(self): song_a = self.addSong( artist=self.addArtist(), genre=self.addGenre(name="A Genre") ) song_b = self.addSong( artist=self.addArtist(), genre=self.addGenre(name="B Genre") ) self.addToQueue(song_a) self.addToQueue(song_b) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=genre" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=genre&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByCreated(self): song_a = self.addSong(artist=self.addArtist()) song_b = self.addSong(artist=self.addArtist()) self.addToQueue(song_a) self.addToQueue(song_b) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=created" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/queue?order_by=created&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testCount(self): song_a = self.addSong(artist=self.addArtist()) song_b = self.addSong(artist=self.addArtist()) song_c = self.addSong(artist=self.addArtist()) self.addToQueue(song_a) self.addToQueue(song_b) self.addToQueue(song_c) result = simplejson.loads( self.httpGet( "/api/v1/queue?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/queue?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) self.assertEquals(result["itemList"][2]["id"], song_c.id) self.assertFalse(result["hasNextPage"]) def testCountAndPage(self): song_a = self.addSong(artist=self.addArtist()) song_b = self.addSong(artist=self.addArtist()) song_c = self.addSong(artist=self.addArtist()) self.addToQueue(song_a) self.addToQueue(song_b) self.addToQueue(song_c) result = simplejson.loads( self.httpGet( "/api/v1/queue?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/queue?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/queue?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertFalse(result["hasNextPage"]) ================================================ FILE: jukebox/jukebox_core/tests/api_songs.py ================================================ # -*- coding: UTF-8 -*- import random, simplejson from jukebox.jukebox_core import api from jukebox.jukebox_core.tests.api import ApiTestBase class ApiSongsTest(ApiTestBase): def testIndexEmpty(self): result = simplejson.loads( self.httpGet( "/api/v1/songs" ).content ) self.assertEquals(len(result["itemList"]), 0) def testIndex(self): song = self.addSong(artist=self.addArtist()) result = simplejson.loads( self.httpGet( "/api/v1/songs" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithSearchTermInTitle(self): fixture = "thisIsATestFixtureString" fixturePart = fixture[0:random.randint(5, len(fixture))] song = self.addSong(artist=self.addArtist(), title=fixture) self.addSong(artist=self.addArtist(), title="AAAAAAAAAAAAAA") result = simplejson.loads( self.httpGet( "/api/v1/songs?search_term=" + fixturePart ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithSearchTermInArtistName(self): fixture = "thisIsATestFixtureString" fixturePart = fixture[0:random.randint(5, len(fixture))] song = self.addSong(artist=self.addArtist(name=fixture)) self.addSong(artist=self.addArtist(name="AAAAAAAAAAAAAA")) result = simplejson.loads( self.httpGet( "/api/v1/songs?search_term=" + fixturePart ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithSearchTermInAlbumTitle(self): fixture = "thisIsATestFixtureString" fixturePart = fixture[0:random.randint(5, len(fixture))] artist = self.addArtist() album = self.addAlbum(title=fixture) song = self.addSong(artist=artist, album=album) self.addSong( artist=artist, album=self.addAlbum(title="AAAAAAAAAAAAAA") ) result = simplejson.loads( self.httpGet( "/api/v1/songs?search_term=" + fixturePart ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithSearchTitle(self): fixture = "thisIsATestFixtureString" fixturePart = fixture[0:random.randint(5, len(fixture))] song = self.addSong(artist=self.addArtist(), title=fixture) self.addSong(artist=self.addArtist(), title="AAAAAAAAAAAAAA") result = simplejson.loads( self.httpGet( "/api/v1/songs?search_title=" + fixturePart ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithSearchArtistName(self): fixture = "thisIsATestFixtureString" fixturePart = fixture[0:random.randint(5, len(fixture))] song = self.addSong(artist=self.addArtist(name=fixture)) self.addSong(artist=self.addArtist(name="AAAAAAAAAAAAAA")) result = simplejson.loads( self.httpGet( "/api/v1/songs?search_artist=" + fixturePart ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithSearchAlbumTitle(self): fixture = "thisIsATestFixtureString" fixturePart = fixture[0:random.randint(5, len(fixture))] artist = self.addArtist() album = self.addAlbum(title=fixture) song = self.addSong(artist=artist, album=album) self.addSong( artist=artist, album=self.addAlbum(title="AAAAAAAAAAAAAA") ) result = simplejson.loads( self.httpGet( "/api/v1/songs?search_album=" + fixturePart ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithFilterYear(self): fixture = 2010 song = self.addSong(artist=self.addArtist(), year=fixture) self.addSong(artist=self.addArtist(), year=2001) result = simplejson.loads( self.httpGet( "/api/v1/songs?filter_year=" + str(fixture) ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithFilterGenre(self): genre = self.addGenre() song = self.addSong(artist=self.addArtist(), genre=genre) self.addSong(artist=self.addArtist(), genre=self.addGenre()) result = simplejson.loads( self.httpGet( "/api/v1/songs?filter_genre=" + str(genre.id) ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithFilterAlbumId(self): artist = self.addArtist() album = self.addAlbum(title="Foo") song = self.addSong(artist=artist, album=album) self.addSong(artist=artist, album=self.addAlbum(title="Bar")) result = simplejson.loads( self.httpGet( "/api/v1/songs?filter_album_id=" + str(album.id) ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexWithFilterArtistId(self): artist = self.addArtist() song = self.addSong(artist=artist) self.addSong(artist=self.addArtist()) result = simplejson.loads( self.httpGet( "/api/v1/songs?filter_artist_id=" + str(artist.id) ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song.id) def testIndexOrderByTitle(self): song_a = self.addSong(artist=self.addArtist(), title="A Title") song_b = self.addSong(artist=self.addArtist(), title="B Title") result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=title" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=title&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByArtist(self): song_a = self.addSong(artist=self.addArtist(name="A Name")) song_b = self.addSong(artist=self.addArtist(name="B Name")) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=artist" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=artist&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByAlbum(self): artist = self.addArtist() album_a = self.addAlbum(title="A Title") album_b = self.addAlbum(title="B Title") song_a = self.addSong(artist=artist, album=album_a) song_b = self.addSong(artist=artist, album=album_b) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=album" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=album&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByYear(self): song_a = self.addSong(artist=self.addArtist(), year=2000) song_b = self.addSong(artist=self.addArtist(), year=2001) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=year" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=year&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByGenre(self): song_a = self.addSong( artist=self.addArtist(), genre=self.addGenre(name="A Name") ) song_b = self.addSong( artist=self.addArtist(), genre=self.addGenre(name="B Name") ) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=genre" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=genre&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testIndexOrderByLength(self): song_a = self.addSong(artist=self.addArtist(), length=100) song_b = self.addSong(artist=self.addArtist(), length=200) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=length" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) result = simplejson.loads( self.httpGet( "/api/v1/songs?order_by=length&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertEquals(result["itemList"][1]["id"], song_a.id) def testCount(self): song_a = self.addSong(artist=self.addArtist()) song_b = self.addSong(artist=self.addArtist()) song_c = self.addSong(artist=self.addArtist()) result = simplejson.loads( self.httpGet( "/api/v1/songs?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/songs?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertEquals(result["itemList"][1]["id"], song_b.id) self.assertEquals(result["itemList"][2]["id"], song_c.id) self.assertFalse(result["hasNextPage"]) def testCountAndPage(self): song_a = self.addSong(artist=self.addArtist()) song_b = self.addSong(artist=self.addArtist()) song_c = self.addSong(artist=self.addArtist()) result = simplejson.loads( self.httpGet( "/api/v1/songs?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_a.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/songs?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_b.id) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/songs?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["id"], song_c.id) self.assertFalse(result["hasNextPage"]) def testGetNextSongRandom(self): song = self.addSong(artist=self.addArtist(), filename=__file__) songs_api = api.songs() result = songs_api.getNextSong() self.assertEquals(result, song) # check if song has been added to history history_api = api.history() result = history_api.index() self.assertEqual(result["itemList"][0]["id"], song.id) def testGetNextSongFromQueue(self): song = self.addSong(artist=self.addArtist(), filename=__file__) # add to queue queue_api = api.queue() queue_api.set_user_id(self.user.id) queue_api.add(song.id) # get next song songs_api = api.songs() result = songs_api.getNextSong() self.assertEquals(result, song) # check if song has been added to history history_api = api.history() result = history_api.index() self.assertEqual(result["itemList"][0]["id"], song.id) # check if song has been removed from queue result = queue_api.index() self.assertEqual(len(result["itemList"]), 0) ================================================ FILE: jukebox/jukebox_core/tests/api_years.py ================================================ # -*- coding: UTF-8 -*- import simplejson from jukebox.jukebox_core.tests.api import ApiTestBase class ApiYearsTest(ApiTestBase): def testIndexEmpty(self): result = simplejson.loads( self.httpGet( "/api/v1/years" ).content ) self.assertEquals(len(result["itemList"]), 0) def testIndex(self): year = 2000 self.addSong(artist=self.addArtist(), year=year) result = simplejson.loads( self.httpGet( "/api/v1/years" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["year"], year) def testIndexOrderBy(self): year_a = 2000 year_b = 2010 self.addSong(artist=self.addArtist(), year=year_a) self.addSong(artist=self.addArtist(), year=year_b) result = simplejson.loads( self.httpGet( "/api/v1/years?order_by=year" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["year"], year_a) self.assertEquals(result["itemList"][1]["year"], year_b) result = simplejson.loads( self.httpGet( "/api/v1/years?order_by=year&order_direction=desc" ).content ) self.assertEquals(len(result["itemList"]), 2) self.assertEquals(result["itemList"][0]["year"], year_b) self.assertEquals(result["itemList"][1]["year"], year_a) def testCount(self): year_a = 2000 year_b = 2005 year_c = 2010 self.addSong(artist=self.addArtist(), year=year_a) self.addSong(artist=self.addArtist(), year=year_b) self.addSong(artist=self.addArtist(), year=year_c) result = simplejson.loads( self.httpGet( "/api/v1/years?count=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["year"], year_a) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/years?count=3" ).content ) self.assertEquals(len(result["itemList"]), 3) self.assertEquals(result["itemList"][0]["year"], year_a) self.assertEquals(result["itemList"][1]["year"], year_b) self.assertEquals(result["itemList"][2]["year"], year_c) self.assertFalse(result["hasNextPage"]) def testCountAndPage(self): year_a = 2000 year_b = 2005 year_c = 2010 self.addSong(artist=self.addArtist(), year=year_a) self.addSong(artist=self.addArtist(), year=year_b) self.addSong(artist=self.addArtist(), year=year_c) result = simplejson.loads( self.httpGet( "/api/v1/years?count=1&page=1" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["year"], year_a) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/years?count=1&page=2" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["year"], year_b) self.assertTrue(result["hasNextPage"]) result = simplejson.loads( self.httpGet( "/api/v1/years?count=1&page=3" ).content ) self.assertEquals(len(result["itemList"]), 1) self.assertEquals(result["itemList"][0]["year"], year_c) self.assertFalse(result["hasNextPage"]) ================================================ FILE: jukebox/jukebox_core/urls.py ================================================ # -*- coding: UTF-8 -*- from django.conf.urls import patterns, url import views urlpatterns = patterns("", url( r"^api/v1/songs$", views.songs.as_view(), name="jukebox_api_songs" ), url( r"^api/v1/songs/skip$", views.songs_skip.as_view(), name="jukebox_api_songs_skip" ), url( r"^api/v1/songs/current", views.songs_current.as_view(), name="jukebox_api_songs_current" ), url( r"^api/v1/artists$", views.artists.as_view(), name="jukebox_api_artists" ), url( r"^api/v1/albums$", views.albums.as_view(), name="jukebox_api_albums" ), url( r"^api/v1/genres$", views.genres.as_view(), name="jukebox_api_genres" ), url( r"^api/v1/years$", views.years.as_view(), name="jukebox_api_years" ), url( r"^api/v1/history$", views.history.as_view(), name="jukebox_api_history" ), url( r"^api/v1/history/my$", views.history_my.as_view(), name="jukebox_api_history_my" ), url( r"^api/v1/favourites$", views.favourites.as_view(), name="jukebox_api_favourites" ), url( r"^api/v1/favourites/(?P[0-9]+)$", views.favourites_item.as_view(), name="jukebox_api_favourites_item" ), url( r"^api/v1/queue$", views.queue.as_view(), name="jukebox_api_queue" ), url( r"^api/v1/queue/(?P[0-9]+)$", views.queue_item.as_view(), name="jukebox_api_queue_item" ), url( r"^api/v1/ping$", views.ping.as_view(), name="jukebox_api_ping" ), ) ================================================ FILE: jukebox/jukebox_core/utils.py ================================================ # -*- coding: UTF-8 -*- from jukebox.jukebox_core.models import Artist, Album, Song, Genre from mutagen.easyid3 import EasyID3 from mutagen.mp3 import MP3, HeaderNotFoundError from mutagen.id3 import ID3NoHeaderError class FileIndexer: def index(self, filename): # skip already indexed if self.is_indexed(filename): return try: id3 = EasyID3(filename) tags = { "artist": None, "title": None, "album": None, "genre": None, "date": None, "length": None, } for k, v in id3.items(): tags[k] = v[0].lower() if tags["artist"] is None or tags["title"] is None: print "Artist or title not set in " + \ filename + " - skipping file" return if tags["artist"] is not None: tags["artist"], created = Artist.objects.get_or_create( Name=tags["artist"] ) if tags["album"] is not None and tags["artist"] is not None: tags["album"], created = Album.objects.get_or_create( Title=tags["album"] ) if tags["genre"] is not None: tags["genre"], created = Genre.objects.get_or_create( Name=tags["genre"] ) if tags["date"] is not None: try: tags["date"] = int(tags["date"]) except ValueError: tags["date"] = None audio = MP3(filename) tags["length"] = int(audio.info.length) song = Song( Artist=tags["artist"], Album=tags["album"], Genre=tags["genre"], Title=tags["title"], Year=tags["date"], Length=tags["length"], Filename=filename ) song.save() except HeaderNotFoundError: print "File contains invalid header data: " + filename except ID3NoHeaderError: print "File does not contain an id3 header: " + filename def delete(self, filename): # single file Song.objects.filter(Filename__exact=filename).delete() # directory Song.objects.filter(Filename__startswith=filename).delete() def is_indexed(self, filename): data = Song.objects.filter(Filename__exact=filename) if not data: return False return True ================================================ FILE: jukebox/jukebox_core/views.py ================================================ # -*- coding: UTF-8 -*- from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated import api, base64 import forms class JukeboxAPIView(APIView): def api_set_user_id(self, request, api): if request.user.is_authenticated(): api.set_user_id(request.user.id) return api class songs(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True page = 1 songs_api = api.songs() songs_api = self.api_set_user_id(request, songs_api) form = forms.SongsForm(request.GET) if form.is_valid(): if not form.cleaned_data["search_term"] == "": songs_api.set_search_term( form.cleaned_data["search_term"] ) if not form.cleaned_data["search_title"] == "": songs_api.set_search_title( form.cleaned_data["search_title"] ) if not form.cleaned_data["search_artist"] == "": songs_api.set_search_artist_name( form.cleaned_data["search_artist"] ) if not form.cleaned_data["search_album"] == "": songs_api.set_search_album_title( form.cleaned_data["search_album"] ) if not form.cleaned_data["filter_artist_id"] is None: songs_api.set_filter_artist_id( form.cleaned_data["filter_artist_id"] ) if not form.cleaned_data["filter_album_id"] is None: songs_api.set_filter_album_id( form.cleaned_data["filter_album_id"] ) if not form.cleaned_data["filter_genre"] is None: songs_api.set_filter_genre( form.cleaned_data["filter_genre"] ) if not form.cleaned_data["filter_year"] is None: songs_api.set_filter_year( form.cleaned_data["filter_year"] ) if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): songs_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": songs_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: songs_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] result = songs_api.index(page) result["form"] = form.cleaned_data return Response( data=result ) class songs_current(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True history = api.history() current = {} try: current = history.getCurrent() except: pass return Response( data=current ) class songs_skip(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True songs_api = api.songs() songs_api.skipCurrentSong() return Response("") class artists(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True page = 1 artists_api = api.artists() form = forms.ArtistsForm(request.GET) if form.is_valid(): if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): artists_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": artists_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: artists_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] return Response( data=artists_api.index(page) ) class albums(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True page = 1 albums_api = api.albums() form = forms.AlbumsForm(request.GET) if form.is_valid(): if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): albums_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": albums_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: albums_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] return Response( data=albums_api.index(page) ) class genres(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True page = 1 genres_api = api.genres() form = forms.GenresForm(request.GET) if form.is_valid(): if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): genres_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": genres_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: genres_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] return Response( data=genres_api.index(page) ) class years(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True page = 1 years_api = api.years() form = forms.YearsForm(request.GET) if form.is_valid(): if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): years_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": years_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: years_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] return Response( data=years_api.index(page) ) class history(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True page = 1 history_api = api.history() history_api = self.api_set_user_id(request, history_api) form = forms.HistoryForm(request.GET) if form.is_valid(): if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): history_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": history_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: history_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] return Response( data=history_api.index(page) ) class history_my(JukeboxAPIView): permissions = (IsAuthenticated, ) def get(self, request): request.session.modified = True page = 1 history_api = api.history_my() history_api = self.api_set_user_id(request, history_api) form = forms.HistoryForm(request.GET) if form.is_valid(): if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): history_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": history_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: history_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] return Response( data=history_api.index(page) ) class queue(JukeboxAPIView): permissions = (IsAuthenticated, ) form = forms.IdForm def get(self, request): request.session.modified = True page = 1 queue_api = api.queue() queue_api = self.api_set_user_id(request, queue_api) form = forms.QueueForm(request.GET) if form.is_valid(): if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): queue_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": queue_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: queue_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] result = queue_api.index(page) for k, v in enumerate(result["itemList"]): result["itemList"][k]["url"] = reverse( "jukebox_api_queue_item", kwargs={"song_id": v["id"]} ) return Response( data=result ) def post(self, request): request.session.modified = True queue_api = api.queue() queue_api = self.api_set_user_id(request, queue_api) try: song_id = queue_api.add(self.request.POST["id"]) return Response( status=status.HTTP_201_CREATED, data={ 'id': int(self.request.POST['id']) }, headers={"Location": reverse( "jukebox_api_queue_item", kwargs={"song_id": song_id} )} ) except ObjectDoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except Exception, e: print e return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) class queue_item(JukeboxAPIView): permissions = (IsAuthenticated, ) form = forms.IdForm def get(self, request, song_id): request.session.modified = True queue_api = api.queue() queue_api = self.api_set_user_id(request, queue_api) try: item = queue_api.get(song_id) item["url"] = reverse( "jukebox_api_queue_item", kwargs={"song_id": item["id"]} ) return Response( data=item ) except ObjectDoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except Exception, e: print e return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) def delete(self, request, song_id): request.session.modified = True queue_api = api.queue() queue_api = self.api_set_user_id(request, queue_api) try: return Response( data=queue_api.remove(song_id) ) except ObjectDoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except: return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) class favourites(JukeboxAPIView): permissions = (IsAuthenticated, ) form = forms.IdForm def get(self, request): request.session.modified = True page = 1 favourites_api = api.favourites() favourites_api = self.api_set_user_id(request, favourites_api) form = forms.FavouritesForm(request.GET) if form.is_valid(): if (not form.cleaned_data["order_by"] == "" and not form.cleaned_data["order_direction"] == ""): favourites_api.set_order_by( form.cleaned_data["order_by"], form.cleaned_data["order_direction"] ) elif not form.cleaned_data["order_by"] == "": favourites_api.set_order_by(form.cleaned_data["order_by"]) if not form.cleaned_data["count"] is None: favourites_api.set_count(form.cleaned_data["count"]) if not form.cleaned_data["page"] is None: page = form.cleaned_data["page"] result = favourites_api.index(page) for k, v in enumerate(result["itemList"]): result["itemList"][k]["url"] = reverse( "jukebox_api_favourites_item", kwargs={"song_id": v["id"]} ) return Response( data=result ) def post(self, request): request.session.modified = True favourites_api = api.favourites() favourites_api = self.api_set_user_id(request, favourites_api) try: song_id = favourites_api.add(self.request.POST["id"]) return Response( status=status.HTTP_201_CREATED, data={ 'id': int(self.request.POST['id']), }, headers={"Location": reverse( "jukebox_api_favourites_item", kwargs={"song_id": song_id} )} ) except ObjectDoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except Exception, e: print e return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) class favourites_item(JukeboxAPIView): permissions = (IsAuthenticated, ) form = forms.IdForm def get(self, request, song_id): request.session.modified = True favourites_api = api.favourites() favourites_api = self.api_set_user_id(request, favourites_api) try: item = favourites_api.get(song_id) item["url"] = reverse( "jukebox_api_favourites_item", kwargs={"song_id": item["id"]} ) return Response( data=item ) except ObjectDoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except Exception, e: print e return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) def delete(self, request, song_id): request.session.modified = True favourites_api = api.favourites() favourites_api = self.api_set_user_id(request, favourites_api) try: return Response( data=favourites_api.remove(song_id) ) except ObjectDoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except Exception, e: print e return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) class ping(JukeboxAPIView): def get(self, request): request.session.modified = True return Response( data= { "ping": True } ) ================================================ FILE: jukebox/jukebox_web/__init__.py ================================================ ================================================ FILE: jukebox/jukebox_web/locale/de/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-11-04 20:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Jens Nistler \n" "Language-Team: LANGUAGE \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #: templates/index.html:5 templates/index.html.py:6 templates/login.html:5 #: templates/login.html.py:6 msgid "Democratic Jukebox - your democratic music player" msgstr "Democratic Jukebox - Dein demokratischer Musikspieler" #: templates/index.html:15 templates/login.html:15 msgid "Jukebox logo" msgstr "Jukebox Logo" #: templates/index.html:18 msgid "Account options" msgstr "Benutzereinstellungen" #: templates/index.html:25 msgid "Search term" msgstr "Suchbegriff" #: templates/index.html:28 templates/index.html.py:140 msgid "Search" msgstr "Suchen" #: templates/index.html:35 msgid "Now playing" msgstr "Aktuell läuft:" #: templates/index.html:45 msgid "Navigation" msgstr "Navigation" #: templates/index.html:50 msgid "Queue" msgstr "Warteschlange" #: templates/index.html:55 msgid "History" msgstr "Zuletzt gespielt" #: templates/index.html:60 msgid "My History" msgstr "Von mir gewählt" #: templates/index.html:65 msgid "Favourites" msgstr "Favoriten" #: templates/index.html:71 msgid "Music" msgstr "Musik" #: templates/index.html:76 msgid "Songs" msgstr "Titel" #: templates/index.html:81 msgid "Artists" msgstr "Künstler" #: templates/index.html:86 msgid "Albums" msgstr "Alben" #: templates/index.html:91 msgid "Genres" msgstr "Musikrichtungen" #: templates/index.html:96 msgid "Years" msgstr "Jahre" #: templates/index.html:109 msgid "Title" msgstr "Titel" #: templates/index.html:113 msgid "Artist" msgstr "Künstler" #: templates/index.html:117 msgid "Album" msgstr "Album" #: templates/index.html:121 msgid "Genre" msgstr "Musikrichtung" #: templates/index.html:123 msgid "All genres" msgstr "Alle Musikrichtungen" #: templates/index.html:130 msgid "Year" msgstr "Jahr" #: templates/index.html:132 msgid "All years" msgstr "Alle Jahre" #: templates/index.html:139 msgid "Reset" msgstr "Leeren" #: templates/index.html:146 msgid "Switch language" msgstr "Sprache ändern" #: templates/index.html:149 msgid "English" msgstr "Englisch" #: templates/index.html:152 msgid "German" msgstr "Deutsch" #: templates/index.html:155 msgid "French" msgstr "Französisch" #: templates/index.html:158 msgid "Brazilian Portuguese" msgstr "Brasilianisches Portugiesisch" #: templates/index.html:161 msgid "Misc" msgstr "Verschiedenes" #: templates/index.html:164 msgid "Logout" msgstr "Abmelden" #: templates/login.html:21 msgid "Login" msgstr "Login" #: templates/login.html:35 #, python-format msgid "Login with your %(backend)s account" msgstr "Melde Dich mit Deinem %(backend)s Konto an" ================================================ FILE: jukebox/jukebox_web/locale/de/LC_MESSAGES/djangojs.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-19 14:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Jens Nistler \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #: static/js/music.js:1 msgid "function" msgstr "" #: static/js/music.js:105 static/js/music.js.py:106 static/js/music.js:414 #: static/js/music.js.py:436 static/js/music.js:458 static/js/music.js.py:475 msgid "Revoke vote" msgstr "Stimme zurückziehen" #: static/js/music.js:122 static/js/music.js.py:123 static/js/music.js:439 #: static/js/music.js.py:461 static/js/music.js:478 msgid "Vote to play" msgstr "Zum Abspielen wählen" #: static/js/music.js:140 static/js/music.js.py:141 static/js/music.js:417 msgid "Support vote" msgstr "Wahl unterstützen" #: static/js/music.js:170 static/js/music.js.py:171 static/js/music.js:420 #: static/js/music.js.py:442 static/js/music.js:463 static/js/music.js.py:481 msgid "Remove from favourites" msgstr "Aus Favoriten entfernen" #: static/js/music.js:186 static/js/music.js.py:187 static/js/music.js:423 #: static/js/music.js.py:445 static/js/music.js:484 msgid "Add to favourites" msgstr "Zu Favoriten hinzufügen" #: static/js/music.js:286 msgid "No data found" msgstr "Keine Daten gefunden" #: static/js/music.js:351 static/js/music.js.py:359 static/js/music.js:367 #: static/js/music.js.py:375 static/js/music.js:388 msgid "Title" msgstr "Titel" #: static/js/music.js:352 static/js/music.js.py:360 static/js/music.js:368 #: static/js/music.js.py:376 static/js/music.js:389 msgid "Artist" msgstr "Künstler" #: static/js/music.js:353 static/js/music.js.py:361 static/js/music.js:369 #: static/js/music.js.py:377 msgid "Album" msgstr "Album" #: static/js/music.js:354 static/js/music.js.py:362 msgid "Votes" msgstr "Stimmen" #: static/js/music.js:355 msgid "First voted" msgstr "Erste Stimme erhalten" #: static/js/music.js:363 static/js/music.js.py:371 msgid "Date added" msgstr "Hinzugefügt" #: static/js/music.js:370 static/js/music.js.py:378 msgid "Genre" msgstr "Musikrichtung" #: static/js/music.js:379 static/js/music.js.py:397 msgid "Year" msgstr "Jahr" #: static/js/music.js:380 msgid "Length" msgstr "Dauer" #: static/js/music.js:384 static/js/music.js.py:393 msgid "Name" msgstr "Name" #: static/js/music.js:451 msgid "Autoplay" msgstr "automatisch" ================================================ FILE: jukebox/jukebox_web/locale/en/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-11-04 20:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Jens Nistler \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: templates/index.html:5 templates/index.html.py:6 templates/login.html:5 #: templates/login.html.py:6 msgid "Democratic Jukebox - your democratic music player" msgstr "Democratic Jukebox - your democratic music player" #: templates/index.html:15 templates/login.html:15 msgid "Jukebox logo" msgstr "Jukebox logo" #: templates/index.html:18 msgid "Account options" msgstr "Account options" #: templates/index.html:25 msgid "Search term" msgstr "Search term" #: templates/index.html:28 templates/index.html.py:140 msgid "Search" msgstr "Search" #: templates/index.html:35 msgid "Now playing" msgstr "Now playing:" #: templates/index.html:45 msgid "Navigation" msgstr "Navigation" #: templates/index.html:50 msgid "Queue" msgstr "Queue" #: templates/index.html:55 msgid "History" msgstr "History" #: templates/index.html:60 #, fuzzy msgid "My History" msgstr "My History" #: templates/index.html:65 msgid "Favourites" msgstr "Favourites" #: templates/index.html:71 msgid "Music" msgstr "Music" #: templates/index.html:76 msgid "Songs" msgstr "Songs" #: templates/index.html:81 msgid "Artists" msgstr "Artists" #: templates/index.html:86 msgid "Albums" msgstr "Albums" #: templates/index.html:91 msgid "Genres" msgstr "Genres" #: templates/index.html:96 msgid "Years" msgstr "Years" #: templates/index.html:109 msgid "Title" msgstr "Title" #: templates/index.html:113 msgid "Artist" msgstr "Artist" #: templates/index.html:117 msgid "Album" msgstr "Album" #: templates/index.html:121 msgid "Genre" msgstr "Genre" #: templates/index.html:123 msgid "All genres" msgstr "All genres" #: templates/index.html:130 msgid "Year" msgstr "Year" #: templates/index.html:132 msgid "All years" msgstr "All years" #: templates/index.html:139 msgid "Reset" msgstr "Reset" #: templates/index.html:146 msgid "Switch language" msgstr "Switch language" #: templates/index.html:149 msgid "English" msgstr "English" #: templates/index.html:152 msgid "German" msgstr "German" #: templates/index.html:155 msgid "French" msgstr "" #: templates/index.html:158 msgid "Brazilian Portuguese" msgstr "Brazilian Portuguese" #: templates/index.html:161 msgid "Misc" msgstr "Misc" #: templates/index.html:164 msgid "Logout" msgstr "Logout" #: templates/login.html:21 msgid "Login" msgstr "Login" #: templates/login.html:35 #, python-format msgid "Login with your %(backend)s account" msgstr "Login with your %(backend)s account" ================================================ FILE: jukebox/jukebox_web/locale/en/LC_MESSAGES/djangojs.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-19 14:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Jens Nistler \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: static/js/music.js:1 msgid "function" msgstr "" #: static/js/music.js:105 static/js/music.js.py:106 static/js/music.js:414 #: static/js/music.js.py:436 static/js/music.js:458 static/js/music.js.py:475 msgid "Revoke vote" msgstr "Revoke vote" #: static/js/music.js:122 static/js/music.js.py:123 static/js/music.js:439 #: static/js/music.js.py:461 static/js/music.js:478 msgid "Vote to play" msgstr "Vote to play" #: static/js/music.js:140 static/js/music.js.py:141 static/js/music.js:417 msgid "Support vote" msgstr "Support vote" #: static/js/music.js:170 static/js/music.js.py:171 static/js/music.js:420 #: static/js/music.js.py:442 static/js/music.js:463 static/js/music.js.py:481 msgid "Remove from favourites" msgstr "Remove from favourites" #: static/js/music.js:186 static/js/music.js.py:187 static/js/music.js:423 #: static/js/music.js.py:445 static/js/music.js:484 msgid "Add to favourites" msgstr "Add to favourites" #: static/js/music.js:286 msgid "No data found" msgstr "No data found" #: static/js/music.js:351 static/js/music.js.py:359 static/js/music.js:367 #: static/js/music.js.py:375 static/js/music.js:388 msgid "Title" msgstr "Title" #: static/js/music.js:352 static/js/music.js.py:360 static/js/music.js:368 #: static/js/music.js.py:376 static/js/music.js:389 msgid "Artist" msgstr "Artist" #: static/js/music.js:353 static/js/music.js.py:361 static/js/music.js:369 #: static/js/music.js.py:377 msgid "Album" msgstr "Album" #: static/js/music.js:354 static/js/music.js.py:362 msgid "Votes" msgstr "Votes" #: static/js/music.js:355 msgid "First voted" msgstr "First voted" #: static/js/music.js:363 static/js/music.js.py:371 msgid "Date added" msgstr "Date added" #: static/js/music.js:370 static/js/music.js.py:378 msgid "Genre" msgstr "Genre" #: static/js/music.js:379 static/js/music.js.py:397 msgid "Year" msgstr "Year" #: static/js/music.js:380 msgid "Length" msgstr "Length" #: static/js/music.js:384 static/js/music.js.py:393 msgid "Name" msgstr "Name" #: static/js/music.js:451 msgid "Autoplay" msgstr "Autoplay" ================================================ FILE: jukebox/jukebox_web/locale/fr/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-11-04 20:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" #: templates/index.html:5 templates/index.html.py:6 templates/login.html:5 #: templates/login.html.py:6 msgid "Democratic Jukebox - your democratic music player" msgstr "" #: templates/index.html:15 templates/login.html:15 msgid "Jukebox logo" msgstr "" #: templates/index.html:18 msgid "Account options" msgstr "Options du compte" #: templates/index.html:25 msgid "Search term" msgstr "Mots recherchés" #: templates/index.html:28 templates/index.html.py:140 msgid "Search" msgstr "Recherche" #: templates/index.html:35 msgid "Now playing" msgstr "Lecture en cours" #: templates/index.html:45 msgid "Navigation" msgstr "" #: templates/index.html:50 msgid "Queue" msgstr "File" #: templates/index.html:55 msgid "History" msgstr "Historique" #: templates/index.html:60 msgid "My History" msgstr "Mon historique" #: templates/index.html:65 msgid "Favourites" msgstr "Favoris" #: templates/index.html:71 msgid "Music" msgstr "Musique" #: templates/index.html:76 msgid "Songs" msgstr "Chansons" #: templates/index.html:81 msgid "Artists" msgstr "Artistes" #: templates/index.html:86 msgid "Albums" msgstr "" #: templates/index.html:91 msgid "Genres" msgstr "" #: templates/index.html:96 msgid "Years" msgstr "Ans" #: templates/index.html:109 msgid "Title" msgstr "Titre" #: templates/index.html:113 msgid "Artist" msgstr "Artiste" #: templates/index.html:117 msgid "Album" msgstr "" #: templates/index.html:121 msgid "Genre" msgstr "" #: templates/index.html:123 msgid "All genres" msgstr "Tous les genres" #: templates/index.html:130 msgid "Year" msgstr "Année" #: templates/index.html:132 msgid "All years" msgstr "Tous les ans" #: templates/index.html:139 msgid "Reset" msgstr "Réinitialiser" #: templates/index.html:146 msgid "Switch language" msgstr "Changer de langue" #: templates/index.html:149 msgid "English" msgstr "Anglais" #: templates/index.html:152 msgid "German" msgstr "Allemand" #: templates/index.html:155 msgid "French" msgstr "Français" #: templates/index.html:158 msgid "Brazilian Portuguese" msgstr "Brésilien Portugais" #: templates/index.html:161 msgid "Misc" msgstr "Divers" #: templates/index.html:164 msgid "Logout" msgstr "Déconnexion" #: templates/login.html:21 msgid "Login" msgstr "Connexion" #: templates/login.html:35 #, python-format msgid "Login with your %(backend)s account" msgstr "Connectez-vous avec votre compte %(backend)s" ================================================ FILE: jukebox/jukebox_web/locale/fr/LC_MESSAGES/djangojs.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-19 14:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Jens Nistler \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: static/js/music.js:1 msgid "function" msgstr "fontion" #: static/js/music.js:105 static/js/music.js.py:106 static/js/music.js:414 #: static/js/music.js.py:436 static/js/music.js:458 static/js/music.js.py:475 msgid "Revoke vote" msgstr "Retirer le vote" #: static/js/music.js:122 static/js/music.js.py:123 static/js/music.js:439 #: static/js/music.js.py:461 static/js/music.js:478 msgid "Vote to play" msgstr "Voter à jouer" #: static/js/music.js:140 static/js/music.js.py:141 static/js/music.js:417 msgid "Support vote" msgstr "Supporter le vote" #: static/js/music.js:170 static/js/music.js.py:171 static/js/music.js:420 #: static/js/music.js.py:442 static/js/music.js:463 static/js/music.js.py:481 msgid "Remove from favourites" msgstr "Retirer des favoris" #: static/js/music.js:186 static/js/music.js.py:187 static/js/music.js:423 #: static/js/music.js.py:445 static/js/music.js:484 msgid "Add to favourites" msgstr "Ajouter aux favoris" #: static/js/music.js:286 msgid "No data found" msgstr "Aucune donnée trouvée" #: static/js/music.js:351 static/js/music.js.py:359 static/js/music.js:367 #: static/js/music.js.py:375 static/js/music.js:388 msgid "Title" msgstr "Titre" #: static/js/music.js:352 static/js/music.js.py:360 static/js/music.js:368 #: static/js/music.js.py:376 static/js/music.js:389 msgid "Artist" msgstr "Artiste" #: static/js/music.js:353 static/js/music.js.py:361 static/js/music.js:369 #: static/js/music.js.py:377 msgid "Album" msgstr "Album" #: static/js/music.js:354 static/js/music.js.py:362 msgid "Votes" msgstr "Votes" #: static/js/music.js:355 msgid "First voted" msgstr "Première voté" #: static/js/music.js:363 static/js/music.js.py:371 msgid "Date added" msgstr "Date d'ajout" #: static/js/music.js:370 static/js/music.js.py:378 msgid "Genre" msgstr "Genre" #: static/js/music.js:379 static/js/music.js.py:397 msgid "Year" msgstr "An" #: static/js/music.js:380 msgid "Length" msgstr "Durée" #: static/js/music.js:384 static/js/music.js.py:393 msgid "Name" msgstr "Nom" #: static/js/music.js:451 msgid "Autoplay" msgstr "Lecture automatique" ================================================ FILE: jukebox/jukebox_web/locale/pt_BR/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-11-04 20:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: templates/index.html:5 templates/index.html.py:6 templates/login.html:5 #: templates/login.html.py:6 msgid "Democratic Jukebox - your democratic music player" msgstr "Democratic Jukebox - o seu player de música democrático" #: templates/index.html:15 templates/login.html:15 msgid "Jukebox logo" msgstr "logo do Jukebox" #: templates/index.html:18 msgid "Account options" msgstr "Opções da Conta" #: templates/index.html:25 msgid "Search term" msgstr "Buscar palavra" #: templates/index.html:28 templates/index.html.py:140 msgid "Search" msgstr "Buscar" #: templates/index.html:35 msgid "Now playing" msgstr "Em execução" #: templates/index.html:45 msgid "Navigation" msgstr "Navegação" #: templates/index.html:50 msgid "Queue" msgstr "Fila" #: templates/index.html:55 msgid "History" msgstr "Histórico" #: templates/index.html:60 msgid "My History" msgstr "Meu Histórico" #: templates/index.html:65 msgid "Favourites" msgstr "Favoritos" #: templates/index.html:71 msgid "Music" msgstr "Música" #: templates/index.html:76 msgid "Songs" msgstr "Músicas" #: templates/index.html:81 msgid "Artists" msgstr "Artistas" #: templates/index.html:86 msgid "Albums" msgstr "Albuns" #: templates/index.html:91 msgid "Genres" msgstr "Gêneros" #: templates/index.html:96 msgid "Years" msgstr "Anos" #: templates/index.html:109 msgid "Title" msgstr "Título" #: templates/index.html:113 msgid "Artist" msgstr "Artista" #: templates/index.html:117 msgid "Album" msgstr "Album" #: templates/index.html:121 msgid "Genre" msgstr "Gênero" #: templates/index.html:123 msgid "All genres" msgstr "Todos os gêneros" #: templates/index.html:130 msgid "Year" msgstr "Anos" #: templates/index.html:132 msgid "All years" msgstr "Todos os anos" #: templates/index.html:139 msgid "Reset" msgstr "Limpar" #: templates/index.html:146 msgid "Switch language" msgstr "Alterar lingua" #: templates/index.html:149 msgid "English" msgstr "Inglês" #: templates/index.html:152 msgid "German" msgstr "Alemão" #: templates/index.html:155 msgid "French" msgstr "Francês" #: templates/index.html:158 msgid "Brazilian Portuguese" msgstr "Português do Brasil" #: templates/index.html:161 msgid "Misc" msgstr "Outros" #: templates/index.html:164 msgid "Logout" msgstr "Sair" #: templates/login.html:21 msgid "Login" msgstr "Acessar" #: templates/login.html:35 #, python-format msgid "Login with your %(backend)s account" msgstr "Acessar com sua conta do %(backend)s" ================================================ FILE: jukebox/jukebox_web/locale/pt_BR/LC_MESSAGES/djangojs.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-12-19 23:24-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: static/js/music.js:236 static/js/music.js.c:237 static/js/music.js.c:646 #: static/js/music.js:707 static/js/music.js.c:767 static/js/music.js.c:813 msgid "Revoke vote" msgstr "Revogar Voto" #: static/js/music.js:252 static/js/music.js.c:253 static/js/music.js.c:710 #: static/js/music.js:770 static/js/music.js.c:816 msgid "Vote to play" msgstr "Vote para tocar" #: static/js/music.js:269 static/js/music.js.c:270 static/js/music.js.c:649 msgid "Support vote" msgstr "Voto de suporte" #: static/js/music.js:297 static/js/music.js.c:298 static/js/music.js.c:652 #: static/js/music.js:713 static/js/music.js.c:772 static/js/music.js.c:819 msgid "Remove from favourites" msgstr "Remover dos favoritos" #: static/js/music.js:312 static/js/music.js.c:313 static/js/music.js.c:655 #: static/js/music.js:716 static/js/music.js.c:822 msgid "Add to favourites" msgstr "Adicionar aos favoritos" #: static/js/music.js:466 msgid "No data found" msgstr "Nenhuma informação encontrada" #: static/js/music.js:572 static/js/music.js.c:581 static/js/music.js.c:590 #: static/js/music.js:599 static/js/music.js.c:608 static/js/music.js.c:621 msgid "Title" msgstr "Título" #: static/js/music.js:573 static/js/music.js.c:582 static/js/music.js.c:591 #: static/js/music.js:600 static/js/music.js.c:609 msgid "Artist" msgstr "Artista" #: static/js/music.js:574 static/js/music.js.c:583 static/js/music.js.c:592 #: static/js/music.js:601 static/js/music.js.c:610 msgid "Album" msgstr "Album" #: static/js/music.js:575 static/js/music.js.c:584 static/js/music.js.c:593 msgid "Votes" msgstr "Votos" #: static/js/music.js:576 msgid "First voted" msgstr "Primeiro voto" #: static/js/music.js:585 static/js/music.js.c:594 static/js/music.js.c:603 msgid "Date added" msgstr "Data de adição" #: static/js/music.js:602 static/js/music.js.c:611 msgid "Genre" msgstr "Gênero" #: static/js/music.js:612 static/js/music.js.c:629 msgid "Year" msgstr "Ano" #: static/js/music.js:613 msgid "Length" msgstr "Tamanho" #: static/js/music.js:617 static/js/music.js.c:625 msgid "Name" msgstr "Nome" #: static/js/music.js:696 static/js/music.js.c:757 msgid "Autoplay" msgstr "Tocar automaticamente" ================================================ FILE: jukebox/jukebox_web/static/css/music.css ================================================ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, textarea, p, blockquote, th, td, button { margin: 0; padding: 0; } table { border-collapse: collapse; border-spacing: 0; width: 100%; } fieldset, img { border: 0; } address, caption, cite, code, dfn, em, strong, th, var { font-style: normal; font-weight: normal; } ol, ul { list-style: none; } caption, th { text-align: left; } h1, h2, h3, h4, h5, h6 { font-size: 100%; font-weight: normal; } label { cursor: pointer; } q:before, q:after { content: ''; } abbr, acronym { border: 0; } strong { font-weight: bold; } hr { display: block; margin: 10px 0; border: 0; border-top: 1px dotted #999999; } blockquote { margin: 10px 5px; padding: 0 10px; border-left: 1px solid #3a8bcc; } a, a:hover, a:visited { color: #ED9005; text-decoration: none; } .invisible { display: none; } body { color: #444444; font-size: 9pt; font-family: verdana, helvetica, sans-serif; line-height: 150%; } /* ------------------------------------------------------------------------------------ header ------------------------------------------------------------------------------------ */ #header { height: 60px; background-color: #F5F5F5; border-bottom: 1px solid #E5E5E5; position: fixed; top: 0; left: 0; z-index: 350; width: 100%; } #logo { position: relative; float: left; width: 220px; padding: 10px 0 0 10px; } #logo span.author { position: absolute; top: 38px; left: 107px; font-size: 8pt; } #logo span.author span.grey { color: #444444; } #search { position: relative; margin-left: 220px; } #search div.searchbox { position: absolute; top: 15px; } #search h1 { position: absolute; top: 22px; left: 28px; font-size: 18pt; font-weight: bold; } #search input.searchterm { position: absolute; left: 25px; height: 30px; width: 330px; margin: 0 !important; padding: 3px 22px 3px 8px; font-family: verdana, helvetica, sans-serif; border: 1px solid #D9D9D9 !important; border-top: 1px solid silver !important; border-radius: 1px; -webkit-border-radius: 1px; -moz-border-radius: 1px; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; background-color: #FFFFFF; } #searchoptions { position: absolute; left: 335px; } span.options { height: 4px; width: 7px; padding: 13px 6px; cursor: pointer; background: url("../img/arrow_down.png") no-repeat center; } span.options:hover, #profile:hover span.options { background: url("../img/arrow_down_active.png") no-repeat center; } span.searchsubmit { display: block; height: 27px; width: 54px; padding: 0 8px; background-color: #F07F09; background: -webkit-linear-gradient(top,#EFA73D,#ED9005); background: -moz-linear-gradient(top,#EFA73D,#ED9005); background: -ms-linear-gradient(top,#EFA73D,#ED9005); background: -o-linear-gradient(top,#EFA73D,#ED9005); background: linear-gradient(top,#EFA73D,#ED9005); border: 1px solid #964E06; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; outline: 0; font-size: 8pt; line-height: 27px; font-weight: bold; text-align: center; color: white; cursor: pointer; } span.searchreset { display: block; height: 27px; width: 54px; padding: 0 8px; float: right; background-color: #AAAAAA; background: -webkit-linear-gradient(top,#AAAAAA,#888888); background: -moz-linear-gradient(top,#AAAAAA,#888888); background: -ms-linear-gradient(top,#AAAAAA,#888888); background: -o-linear-gradient(top,#AAAAAA,#888888); background: linear-gradient(top,#AAAAAA,#888888); border: 1px solid #444444; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; outline: 0; font-size: 8pt; line-height: 27px; font-weight: bold; text-align: center; color: white; cursor: pointer; } #search span.searchsubmit { position: absolute; left: 370px; margin-right: 16px; } #searchdetails { display: none; position: absolute; top: 44px; left: 245px; background-color: #FFFFFF; width: 318px; border: 1px solid #CCC; padding: 5px; z-index: 400; } #searchdetails label { display: block; font-size: 8pt; margin-bottom: 3px; } #searchdetails input[type=text], #searchdetails select { width: 100%; padding: 3px; font-family: verdana, helvetica, sans-serif; border: 1px solid #D9D9D9 !important; border-top: 1px solid silver !important; border-radius: 1px; -webkit-border-radius: 1px; -moz-border-radius: 1px; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; } #searchdetails div.option { margin-bottom: 10px; } #currentSong { position: relative; text-align: left; margin: 12px 120px 0 680px; } #currentSong strong { display: none; } #profile { float: right; margin: 20px 20px 0 0; text-align: right; cursor: pointer; } #profile span.username { float: left; margin-right: 5px; } #accountoptions { display: none; position: absolute; top: 44px; right: 20px; background-color: #FFFFFF; width: 200px; border: 1px solid #CCC; padding: 15px 5px; z-index: 400; } #accountoptions h1 { font-size: 11pt; padding-left: 10px; } #accountoptions ul { margin: 5px 0 15px 0; } #accountoptions ul li { padding: 0 0 5px 20px; border-left: 3px solid #FFFFFF; } #accountoptions ul li.active { font-weight: bold; border-left: 3px solid #ED9005; } /* ------------------------------------------------------------------------------------ content ------------------------------------------------------------------------------------ */ #content { margin-top: 75px; } #sidebar { width: 180px; margin-right: 10px; position: fixed; top: 76px; left: 0; z-index: 350; } #sidebar h1 { font-size: 12pt; padding-left: 20px; } #sidebar ul { margin: 5px 0 15px 0; } #sidebar ul li { padding: 0 0 5px 35px; border-left: 3px solid #FFFFFF; } #sidebar ul li.second { padding: 0 0 5px 55px; } #sidebar ul li.active { font-weight: bold; border-left: 3px solid #ED9005; } #main_spacer { position: fixed; top: 61px; left: 250px; right: 30px; height: 15px; width: 100%; background-color: #FFFFFF; z-index: 350; } #main { position: absolute; top: 76px; left: 200px; right: 30px; padding-bottom: 15px; } #main div.login { margin-top: 30px; } #main div.login div { margin-bottom: 30px; } #main p.login_error { color: #FF0000; } #main div.noContent { } #main table.list { width: 100%; } #main table.list th { padding: 0 0 10px 0; cursor: pointer; } #main table.list th:hover { color: #ED9005; } #main table.list th.sort_asc:after { content: " ↗"; } #main table.list th.sort_desc:after { content: " ↘"; } #main table.list th.options { width: 6%; min-width: 80px; } #main table.list th.song_title, #main table.list th.favourite_title { width: 27%; } #main table.list th.song_artist, #main table.list th.favourite_artist { width: 22%; } #main table.list th.song_album, #main table.list th.favourite_album { width: 20%; } #main table.list th.favourite_added { width: 15%; } #main table.list th.song_year { width: 7%; } #main table.list th.song_length { width: 8%; } #main table.list th.song_genre, #main table.list th.favourite_genre { width: 10% } #main table.list th.options_small { width: 3%; } #main table.list th.name, #main table.list th.year { width: 97%; } #main table.list th.album_title { width: 49% } #main table.list th.album_artist { width: 48%; } #main table.list td { height: 34px; font-size: 9pt; background-color: #F5F5F5; border-bottom: 1px solid #E5E5E5; padding: 5px 0 5px 15px; overflow: hidden; } #main table.list td div.voteTooltip { display: none; position: fixed; border: 1px solid #CCC; background-color: #FFFFFF; padding: 5px; z-index: 400; } #main table.list td.filter { cursor: pointer; } #main table.list td.filter:hover { background-color: #EFA73D; } #main table.list td img { margin-right: 10px; cursor: pointer; } ================================================ FILE: jukebox/jukebox_web/static/js/music.js ================================================ if (typeof gettext != "function") { gettext = function(identifier) { return identifier; } } Music = { pageNum: 1, hasNextPage: true, options: {}, searchOptions: {}, infiniteScrollActive: false, sessionPing: 60000, remaining: 0, csrf_token: null, init: function() { Music.csrf_token = $('#csrf_token input[name="csrfmiddlewaretoken"]').val(); jQuery.ajaxSetup({ headers: { 'X-CSRFToken': Music.csrf_token } }); $(".loadList").bind("click", function() { Music.options = {}; Music.setActiveMenu($(this)); Music.loadList($(this).attr("href")); return false; }); $("#searchform span.searchsubmit").bind("click", function() { Music.options = {"search_term": $("input.searchterm").val()}; Music.loadList("/api/v1/songs"); Music.setActiveMenu($("#sidebar ul li a.loadSongs")); return false; }); $("#searchform").bind("submit", function() { Music.options = {"search_term": $("input.searchterm").val()}; Music.loadList("/api/v1/songs"); Music.setActiveMenu($("#sidebar ul li a.loadSongs")); return false; }); $("#searchdetailsform span.searchsubmit").bind("click", function() { Music.options = Music.getSearchOptions(); Music.loadList("/api/v1/songs"); Music.setActiveMenu($("#sidebar ul li a.loadSongs")); $("#searchoptions").click(); return false; }); $("#searchdetailsform span.searchreset").bind("click", function() { $("#search_title").val(""); $("#search_artist").val(""); $("#search_album").val(""); $("#search_genre").val(""); $("#search_year").val(""); return false; }); $("#searchdetailsform").bind("submit", function() { Music.options = Music.getSearchOptions(); Music.loadList("/api/v1/songs"); Music.setActiveMenu($("#sidebar ul li a.loadSongs")); $("#searchoptions").click(); return false; }); // chrome doesn't fire submit on #searchdetailsform -> dirty workaround $("#search_title, #search_artist, #search_album, #search_album").bind("keydown", function() { if (event.which == 13) { Music.options = Music.getSearchOptions(); Music.loadList("/api/v1/songs"); Music.setActiveMenu($("#sidebar ul li a.loadSongs")); $("#searchoptions").click(); return false; } }); $("#searchoptions").bind("click", function() { if ($("#searchdetails:visible").length == 1) { $(document).unbind("click.search"); $("#searchdetails").hide(); } else { // reset all $("#searchdetailsform span.searchreset").click(); // fill form by current search options $.each(Music.searchOptions, function(key, value) { if (key != "genre_id" && value.substr(0, 1) == "(") { value = value.substr(1, value.length - 2); } switch (key) { case "title": $("#search_title").val(value); break; case "artist": $("#search_artist").val(value); break; case "album": $("#search_album").val(value); break; case "genre_id": $("#search_genre").val(value); break; case "year": $("#search_year").val(value); break; } }); $("#searchdetails").show(); $(document).bind("click.search", function(event) { if ($(event.target).closest("#searchdetails").length == 0 && $(event.target).closest("#searchoptions").length == 0) { $("#searchoptions").click(); } }); } }); $("#profile").bind("click", function() { if ($("#accountoptions:visible").length == 1) { $(document).unbind("click.account"); $("#accountoptions").hide(); } else { $("#accountoptions").show(); $(document).bind("click.account", function(event) { if ($(event.target).closest("#accountoptions").length == 0 && $(event.target).closest("#profile").length == 0) { $("#profile").click(); } }); } }); $("#main table.list td.filter").live("click", function() { var value = $(this).attr("data-value"); if ($(this).hasClass("filter_artist")) { Music.options = {"filter_artist_id": value}; } else if ($(this).hasClass("filter_album")) { Music.options = {"filter_album_id": value}; } else if ($(this).hasClass("filter_genre")) { Music.options = {"filter_genre": value}; } else if ($(this).hasClass("filter_year")) { Music.options = {"filter_year": value}; } else if ($(this).hasClass("search_title")) { Music.options = {"search_title": value}; } Music.loadList("/api/v1/songs"); Music.setActiveMenu($("#sidebar ul li a.loadSongs")); return false; }); $("#main table.list th").live("click", function() { if ($(this).hasClass("sort_asc")) { Music.options.order_direction = "desc"; } else { Music.options.order_direction = "asc"; } if ($(this).hasClass("sort_title")) { Music.options.order_by = "title"; } else if ($(this).hasClass("sort_artist")) { Music.options.order_by = "artist"; } else if ($(this).hasClass("sort_album")) { Music.options.order_by = "album"; } else if ($(this).hasClass("sort_genre")) { Music.options.order_by = "genre"; } else if ($(this).hasClass("sort_year")) { Music.options.order_by = "year"; } else if ($(this).hasClass("sort_length")) { Music.options.order_by = "length"; } else if ($(this).hasClass("sort_created")) { Music.options.order_by = "created"; } else if ($(this).hasClass("sort_votes")) { Music.options.order_by = "votes"; } switch ($(this).closest("tr").attr("class")) { case "queue": Music.loadList("/api/v1/queue"); break; case "history": Music.loadList("/api/v1/history"); break; case "history_my": Music.loadList("/api/v1/history/my"); break; case "favourites": Music.loadList("/api/v1/favourites"); break; case "songs": Music.loadList("/api/v1/songs"); break; case "artists": Music.loadList("/api/v1/artists"); break; case "albums": Music.loadList("/api/v1/albums"); break; case "genres": Music.loadList("/api/v1/genres"); break; case "years": Music.loadList("/api/v1/years"); break; } return false; }); $("#main table.list img.queue_add").live("click", function() { $.ajax({ url: "/api/v1/queue", type: "POST", data: { "id": $(this).attr("data-id") }, success: function(data) { var item = $("img.queue_add[data-id=" + data.id + "]"); item.attr("src", "/static/img/queue_active.png"); item.removeClass("queue_add"); item.addClass("queue_remove"); item.attr("id", item.attr("id").replace(/add/, "remove")); item.attr("alt", gettext("Revoke vote")); item.attr("title", gettext("Revoke vote")); item.closest("tr").find(".voteCount").html(data.count); } }); return false; }); $("#main table.list img.queue_remove").live("click", function() { var success = null; if ($(this).closest("tr.row_queue").length == 0) { success = function(data) { var item = $("img.queue_remove[data-id=" + data.id + "]"); item.attr("src", "/static/img/queue.png"); item.removeClass("queue_remove"); item.addClass("queue_add"); item.attr("alt", gettext("Vote to play")); item.attr("title", gettext("Vote to play")); }; } else { success = function(data) { var item = $("img.queue_remove[data-id=" + data.id + "]"); if (data.count == 0) { item.closest("tr").fadeOut(1000, function() { item.closest("tr").remove(); }); return; } item.attr("src", "/static/img/queue.png"); item.removeClass("queue_remove"); item.addClass("queue_add"); item.attr("alt", gettext("Support vote")); item.attr("title", gettext("Support vote")); item.closest("tr").find(".voteCount").html(data.count); }; } $.ajax({ url: "/api/v1/queue/" + $(this).attr("data-id"), type: "DELETE", success: function(data) { success(data); } }); return false; }); $("#main table.list img.favourite_add").live("click", function() { $.ajax({ url: "/api/v1/favourites", type: "POST", data: { "id": $(this).attr("data-id") }, success: function(data) { var item = $("img.favourite_add[data-id=" + data.id + "]"); item.attr("src", "/static/img/favourite_active.png"); item.removeClass("favourite_add"); item.addClass("favourite_remove"); item.attr("alt", gettext("Remove from favourites")); item.attr("title", gettext("Remove from favourites")); } }); return false; }); $("#main table.list img.favourite_remove").live("click", function() { var success = null; if ($(this).closest("tr.row_favourites").length == 0) { success = function(data) { var item = $("img.favourite_remove[data-id=" + data.id + "]"); item.attr("src", "/static/img/favourite.png"); item.removeClass("favourite_remove"); item.addClass("favourite_add"); item.attr("alt", gettext("Add to favourites")); item.attr("title", gettext("Add to favourites")); }; } else { success = function(data) { var item = $("img.favourite_remove[data-id=" + data.id + "]"); item.closest("tr").fadeOut(1000, function() { item.closest("tr").remove(); }); }; } $.ajax({ url: "/api/v1/favourites/" + $(this).attr("data-id"), type: "DELETE", success: function(data) { success(data); } }); return false; }); $("td.voteCount").live({ mouseenter: function() { var element = $(this); var tooltip = $(this).find('div.voteTooltip'); if (tooltip.length == 1) { tooltip.css('left', element.offset().left - $(window).scrollLeft() + 50); tooltip.css('top', element.offset().top - $(window).scrollTop() + 10); tooltip.show(); } }, mouseleave: function() { var tooltip = $(this).find('div.voteTooltip'); if (tooltip.length == 1) { tooltip.hide(); } } }); $.ajaxSetup({ type: "GET", cache: false, dataType: "json" }); Music.getCurrentSong(); Music.ping(); Music.loadList("/api/v1/queue"); Music.setActiveMenu($("#sidebar ul li a.loadQueue")); }, ping: function() { $.ajax({ url: "/api/v1/ping", success: function() { setTimeout("Music.ping()", Music.sessionPing); } }); }, getCurrentSong: function() { $.ajax({ url: "/api/v1/songs/current", success: function(data) { if ("id" in data) { $('#currentSong strong').show(); $('#currentSong span.songTitle').html(data.artist.name + " - " + data.title); Music.remaining = data.remaining; Music.updateTimeLeft(); } else { $('#currentSong strong').hide(); } } }); }, updateTimeLeft: function() { if (0 == Music.remaining) { $('#currentSong span.timeRemaining').hide(); setTimeout("Music.getCurrentSong()", 1000); return; } else if (Music.remaining > 0) { var minutes = parseInt(Music.remaining / 60); var secondsInt = Music.remaining % 60; var seconds = secondsInt.toString(); if (secondsInt < 10) { seconds = "0" + seconds; } $('#currentSong span.timeRemaining').show(); $('#currentSong span.timeRemaining').html("(" + minutes + ":" + seconds + ")"); Music.remaining--; } setTimeout("Music.updateTimeLeft()", 1000); }, getSearchOptions: function() { var options = {}; var value = $("#search_title").val(); if (value.length > 0) { options.search_title = value; } var value = $("#search_artist").val(); if (value.length > 0) { options.search_artist = value; } var value = $("#search_album").val(); if (value.length > 0) { options.search_album = value; } var item = $("#search_genre").find("option:selected"); if (item.length > 0 && item.val().length > 0) { options.filter_genre = item.val(); } var item = $("#search_year").find("option:selected"); if (item.length > 0 && item.val().length > 0) { options.filter_year = item.val(); } return options; }, setActiveMenu: function(item) { $("#sidebar").find("li").removeClass("active"); item.closest("li").addClass("active"); }, loadList: function(url) { Music.pageNum = 1; Music.hasNextPage = false; Music.searchOptions = {}; $("#searchdetailsform span.searchreset").click(); // build options Music.options.page = Music.pageNum; Music.options.count = 30; $.ajax({ url: url, data: Music.options, success: function(data) { $(window).unbind("scroll"); $(window).scrollTop(0); Music.hasNextPage = data.hasNextPage; if (data.itemList.length > 0) { $("#main").html(Music.renderTable(data)); $("#main table.list tbody").append(Music.renderData(data)); } else { $("#main").html("
" + gettext("No data found") + "
"); } // set search term - don't iterate to get correct order Music.searchOptions = data.search; terms = []; if (data.search.title) { terms.push("title:" + data.search.title); } if (data.search.artist) { terms.push("artist:" + data.search.artist); } if (data.search.album) { terms.push("album:" + data.search.album); } if (data.search.genre) { terms.push("genre:" + data.search.genre); } if (data.search.year) { terms.push("year:" + data.search.year); } if (data.search.term) { terms.push(data.search.term); } $("input.searchterm").val(terms.join(" ")); // load data until page is populated if (Music.hasNextPage && Music.getScrollHeight() <= $(document).height()) { $(window).unbind("scroll"); Music.loadItems(url, Music.options); } } }); }, loadOnScroll: function(event) { if (Music.infiniteScrollActive === true) { return false; } if (Music.hasNextPage && Music.getScrollHeight() > Music.getDocumentHeight()) { $(window).unbind("scroll"); Music.loadItems(event.data.url, event.data.options); } }, getScrollHeight: function() { return $(window).scrollTop() + $(window).height(); }, getDocumentHeight: function() { return $(document).height() - $(document).height() * 0.2; }, loadItems: function(url, options) { if (Music.hasNextPage === false) { return false; } if (typeof options == "undefined") { options = {}; } Music.infiniteScrollActive = true; Music.pageNum = Music.pageNum + 1; options.page = Music.pageNum; options.count = 30; $.ajax({ url: url.replace(/\[page\]/, Music.pageNum), data: options, success: function(data) { Music.hasNextPage = data.hasNextPage; $("#main table.list tbody").append(Music.renderData(data)); if (Music.hasNextPage) { $(window).bind("scroll", {url: url, options: options}, Music.loadOnScroll); } Music.infiniteScrollActive = false; } }); }, getOrderClass: function(field, data) { for (var key in data.order) { var item = data.order[key]; if (item.field == field) { switch (item.direction) { case "asc": return " sort_asc"; case "desc": return " sort_desc"; } } } return ""; }, renderTable: function(data) { var html = ""; switch (data.type) { case "queue": html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; break; case "history": html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; break; case "history/my": html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; break; case "favourites": html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; break; case "songs": html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; html+= ""; break; case "artists": html+= ""; html+= ""; break; case "albums": html+= ""; html+= ""; break; case "genres": html+= ""; html+= ""; break; case "years": html+= ""; html+= ""; break; } $(window).bind("scroll", {url: "/api/v1/" + data.type}, Music.loadOnScroll); html+= "
 " + gettext("Title") + "" + gettext("Artist") + "" + gettext("Album") + "" + gettext("Votes") + "" + gettext("First voted") + "
 " + gettext("Title") + "" + gettext("Artist") + "" + gettext("Album") + "" + gettext("Votes") + "" + gettext("Date added") + "
 " + gettext("Title") + "" + gettext("Artist") + "" + gettext("Album") + "" + gettext("Votes") + "" + gettext("Date added") + "
 " + gettext("Title") + "" + gettext("Artist") + "" + gettext("Album") + "" + gettext("Genre") + "" + gettext("Date added") + "
 " + gettext("Title") + "" + gettext("Artist") + "" + gettext("Album") + "" + gettext("Genre") + "" + gettext("Year") + "" + gettext("Length") + "
" + gettext("Name") + "
" + gettext("Title") + "
" + gettext("Name") + "
" + gettext("Year") + "
"; return html; }, renderData: function(data) { var html = ""; $.each(data.itemList, function(index, item) { switch (data.type) { case "queue": html+= ""; html+= ""; if (item.queued) { html+= "\"""; } else { html+= "\"""; } if (item.favourite) { html+= "\"""; } else { html+= "\"""; } html+= ""; // title if (item.title != null) { html+= "" + item.title + ""; } else { html+= " "; } // artist if (item.artist.id != null) { html+= "" + item.artist.name + ""; } else { html+= " "; } // album if (item.album.id != null) { html+= "" + item.album.title + ""; } else { html+= " "; } html+= ""; if (item.votes > 0) { if (item.users.length == item.votes) { html+= "
    "; for (var key in item.users) { var user = item.users[key]; html+= "
  • " + user.name + "
  • " } html+= "
"; } html+= item.votes; } else { html+= gettext("Autoplay"); } html+= ""; html+= "" + item.created + ""; break; case "history": case "history/my": html+= ""; html+= ""; if (item.queued) { html+= "\"""; } else { html+= "\"""; } if (item.favourite) { html+= "\"""; } else { html+= "\"""; } html+= ""; // title if (item.title != null) { html+= "" + item.title + ""; } else { html+= " "; } // artist if (item.artist.id != null) { html+= "" + item.artist.name + ""; } else { html+= " "; } // album if (item.album.id != null) { html+= "" + item.album.title + ""; } else { html+= " "; } html+= ""; if (item.votes > 0) { if (item.users.length == item.votes) { html+= "
    "; for (var key in item.users) { var user = item.users[key]; html+= "
  • " + user.name + "
  • " } html+= "
"; } html+= item.votes; } else { html+= gettext("Autoplay"); } html+= ""; html+= "" + item.created + ""; break; case "favourites": html+= ""; html+= ""; if (item.queued) { html+= "\"""; } else { html+= "\"""; } html+= "\"""; html+= ""; // title if (item.title != null) { html+= "" + item.title + ""; } else { html+= " "; } // artist if (item.artist.id != null) { html+= "" + item.artist.name + ""; } else { html+= " "; } // album if (item.album.id != null) { html+= "" + item.album.title + ""; } else { html+= " "; } // genre if (item.genre.id != null) { html+= "" + item.genre.name + ""; } else { html+= " "; } html+= "" + item.created + ""; break; case "songs": html+= ""; html+= ""; if (item.queued) { html+= "\"""; } else { html+= "\"""; } if (item.favourite) { html+= "\"""; } else { html+= "\"""; } html+= ""; // html+= "" + ((item.title != null) ? : "") + "" + item.id + ""; // title if (item.title != null) { html+= "" + item.title + ""; } else { html+= " "; } // artist if (item.artist.id != null) { html+= "" + item.artist.name + ""; } else { html+= " "; } // album if (item.album.id != null) { html+= "" + item.album.title + ""; } else { html+= " "; } // genre if (item.genre.id != null) { html+= "" + item.genre.name + ""; } else { html+= " "; } // year if (item.year != null) { html+= "" + item.year + ""; } else { html+= " "; } // length var minutes = parseInt(item.length / 60); var seconds = item.length % 60; if (seconds < 10) { seconds = "0" + seconds; } html+= "" + minutes + ":" + seconds + ""; break; case "artists": html+= ""; html+= "" + item.artist + ""; break; case "albums": html+= ""; html+= "" + item.album + ""; break; case "genres": html+= ""; html+= "" + item.genre + ""; break; case "years": html+= ""; html+= "" + item.year + ""; break; } html+= ""; }); return html; } }; $(document).ready(function() { Music.init(); }); ================================================ FILE: jukebox/jukebox_web/templates/index.html ================================================ {% load i18n %} {% trans "Democratic Jukebox - your democratic music player" %}
{% trans 'Reset' %} {% trans 'Search' %}
{% csrf_token %}
================================================ FILE: jukebox/jukebox_web/templates/login.html ================================================ {% load i18n %} {% trans 'Democratic Jukebox - your democratic music player' %}
{% if error %} {% for msg in error %} {% endfor %} {% endif %}
================================================ FILE: jukebox/jukebox_web/urls.py ================================================ # -*- coding: UTF-8 -*- from django.conf.urls import patterns, url from jukebox.jukebox_core.models import QueueFeed import views js_info_dict = { 'packages': ( 'jukebox_web', ), } urlpatterns = patterns("", url(r"^$", views.index, name="jukebox_web_index"), url(r"^login$", views.login, name="jukebox_web_login"), url(r"^login/error$", views.login_error, name="jukebox_web_login_error"), url( r"^language/set/(?P[-a-z]{5}|[a-z]{2})", views.language, name="jukebox_web_language" ), url(r"^logout$", views.logout, name="jukebox_web_logout"), url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict), # RSS feed url (r'^feed/$', QueueFeed()), ) ================================================ FILE: jukebox/jukebox_web/views.py ================================================ # -*- coding: UTF-8 -*- from django.shortcuts import render_to_response from django.core.context_processors import csrf from django.http import HttpResponseRedirect from django.contrib.auth import logout as auth_logout from django.template import RequestContext from django.contrib.messages.api import get_messages from django.conf import settings from jukebox.jukebox_core.models import Song, Genre def index(request): if request.user.is_authenticated(): request.session.set_expiry(settings.SESSION_TTL) genres = Genre.objects.all() years = Song.objects.values("Year").distinct() years = years.exclude(Year=None).exclude(Year=0).order_by("Year") context = { "username": request.user.get_full_name(), "genres": genres, "years": years } context.update(csrf(request)) return render_to_response('index.html', context) else: return HttpResponseRedirect('login') def login(request): if request.user.is_authenticated(): return HttpResponseRedirect('index') else: return render_to_response( 'login.html', { "backends": settings.SOCIAL_AUTH_ENABLED_BACKENDS, }, RequestContext(request) ) def login_error(request): messages = get_messages(request) return render_to_response( 'login.html', {"error": messages}, RequestContext(request) ) def logout(request): auth_logout(request) return HttpResponseRedirect('/') def language(request, language): from django.utils.translation import check_for_language from django.utils import translation response = HttpResponseRedirect("/") if language and check_for_language(language): if hasattr(request, "session"): request.session["django_language"] = language else: response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language) translation.activate(language) return response ================================================ FILE: jukebox/manage.py ================================================ #!/usr/bin/env python from django.core.management import execute_manager import imp try: imp.find_module('settings') # Assumed to be in the same directory. except ImportError: import sys sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) sys.exit(1) import settings if __name__ == "__main__": execute_manager(settings) ================================================ FILE: jukebox/settings.py ================================================ import os import pkgutil import sys BASE_DIR = os.path.normpath(os.path.dirname(__file__)) DEBUG = False TEMPLATE_DEBUG = DEBUG ADMINS = () MANAGERS = ADMINS JUKEBOX_STORAGE_PATH = os.path.join( os.path.expanduser('~'), '.jukebox', ) if not os.path.exists(JUKEBOX_STORAGE_PATH): try: os.makedirs(JUKEBOX_STORAGE_PATH, 0750) except os.error: JUKEBOX_STORAGE_PATH = BASE_DIR DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join( JUKEBOX_STORAGE_PATH, 'db.sqlite' ), } } SITE_ID = 1 TIME_ZONE = 'Europe/Berlin' LANGUAGE_CODE = 'en-us' LANGUAGES = ( ('de', 'Deutsch'), ('en', 'English'), ('fr', 'French'), ('pt-br', 'Brazilian Portuguese'), ) USE_I18N = True USE_L10N = True STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_URL = '/static/' MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) TEMPLATE_DIRS = ( os.path.join(BASE_DIR, 'jukebox_web/templates'), ) ADMIN_MEDIA_PREFIX = '/static/admin/' ROOT_URLCONF = 'jukebox.urls' LOCALE_PATHS = ( os.path.join(BASE_DIR, 'jukebox_web/locale'), ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', 'django.contrib.admindocs', 'rest_framework', 'social_auth', 'south', 'jukebox_core', 'jukebox_web', ) # automatically add jukebox plugins for item in pkgutil.iter_modules(): if str(item[1]).startswith('jukebox_'): INSTALLED_APPS += (str(item[1]), ) TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.contrib.messages.context_processors.messages', 'social_auth.context_processors.social_auth_by_type_backends', ) LOGIN_URL = '/login' LOGIN_ERROR_URL = '/login/error' LOGIN_REDIRECT_URL = '/' SESSION_TTL = 300 sys.path.append(JUKEBOX_STORAGE_PATH) try: from settings_local import * except ImportError: pass ================================================ FILE: jukebox/settings_local.example.py ================================================ ADMINS = ( ("[admin_user]", "[admin_email]"), ) DEBUG = True TEMPLATE_DEBUG = DEBUG SECRET_KEY = "yourSecretKey" AUTHENTICATION_BACKENDS = ( "django.contrib.auth.backends.ModelBackend", [auth_backends] ) SOCIAL_AUTH_ENABLED_BACKENDS = ([auth_backends_enabled]) [auth_data] ================================================ FILE: jukebox/urls.py ================================================ # -*- coding: UTF-8 -*- from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns("", url(r"^admin/", include(admin.site.urls)), url(r"^admin/doc/", include("django.contrib.admindocs.urls")), url(r'', include('jukebox.jukebox_web.urls')), url(r'', include('jukebox.jukebox_core.urls')), url(r'', include('social_auth.urls')), ) ================================================ FILE: requirements.txt ================================================ Django==1.4.5 mutagen==1.21 django-social-auth==0.7.20 djangorestframework==2.2.1 simplejson==3.1.0 South==0.7.6 ================================================ FILE: setup.py ================================================ # -*- coding: UTF-8 -*- import glob from setuptools import setup, find_packages setup( name="jukebox", packages=find_packages(), version="0.4.1", description="Democratic Jukebox - your democratic music player", author="Jens Nistler", author_email="opensource@jensnistler.de", url="http://jensnistler.de/", download_url='http://github.com/lociii/jukebox', keywords=["jukebox", "music", "mp3"], license="MIT", classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 2", "Development Status :: 3 - Alpha", "Environment :: Web Environment", "Intended Audience :: End Users/Desktop", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Multimedia :: Sound/Audio :: Players", ], install_requires=[ "Django==1.4.5", "mutagen==1.21", "django-social-auth==0.7.20", "djangorestframework==2.2.1", "simplejson==3.1.0", "South==0.7.6", ], include_package_data=True, scripts=glob.glob("bin/*"), long_description=open("README.rst").read() )