Full Code of lociii/jukebox for AI

master eb9d1795f022 cached
63 files
252.6 KB
61.6k tokens
244 symbols
1 requests
Download .txt
Showing preview only (270K chars total). Download the full file or copy to clipboard to get everything.
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 <http://pypi.python.org/pypi/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 <https://github.com/omab/django-social-auth>`_ for more authentication providers)

Setup
==================

Install `virtualenvwrapper <https://pypi.python.org/pypi/virtualenvwrapper>`_ via `pip <http://pypi.python.org/pypi/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 <https://github.com/lociii/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 <https://github.com/lociii/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 <https://github.com/lociii/jukebox_live_indexer>`_ for details and startup command.

API
=============

jukebox_core provides a fully fledged REST API for authenticated users. See `API reference <https://github.com/lociii/jukebox/blob/master/jukebox/jukebox_core/docs/API.rst>`_

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 <https://github.com/lociii/jukebox/blob/master/LICENSE.rst>`_

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 <https://www.openshift.com/wiki/github-workflow-for-submitting-pull-requests>`_ for how to do this.

Contributors
=============
- Brazilian portuguese translation by `Luan Fonseca de Farias <https://github.com/luanfonceca>`_
- Bugfixes by `Peter Hoffmann <https://github.com/hoffmann>`_
- Bugfixes by `Amir H. Hajizamani <https://github.com/amirhhz>`_
- Bugfixes by `Gabriel Duman <https://github.com/gabber7>`_
- Bugfixes by `Steffen Zieger <https://github.com/saz>`_
- Bugfixes by `Jonas Baumann <https://github.com/jone>`_
- Bugfixes by `imithun <https://github.com/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 <https://github.com/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<song_id>[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<song_id>[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 <EMAIL@ADDRESS>, 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 <opensource@jensnistler.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <EMAIL@ADDRESS>, 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 <opensource@jensnistler.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <EMAIL@ADDRESS>, 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 <opensource@jensnistler.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <EMAIL@ADDRESS>, 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 <opensource@jensnistler.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <EMAIL@ADDRESS>, 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 <opensource@jensnistler.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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"


===================================
Download .txt
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
Download .txt
SYMBOL INDEX (244 symbols across 20 files)

FILE: jukebox/jukebox_core/admin.py
  class ArtistAdmin (line 7) | class ArtistAdmin(admin.ModelAdmin):
  class GenreAdmin (line 12) | class GenreAdmin(admin.ModelAdmin):
  class AlbumAdmin (line 16) | class AlbumAdmin(admin.ModelAdmin):
  class SongAdmin (line 21) | class SongAdmin(admin.ModelAdmin):
  class QueueAdmin (line 26) | class QueueAdmin(admin.ModelAdmin):
  class HistoryAdmin (line 30) | class HistoryAdmin(admin.ModelAdmin):
  class FavouriteAdmin (line 34) | class FavouriteAdmin(admin.ModelAdmin):

FILE: jukebox/jukebox_core/api.py
  class api_base (line 15) | class api_base:
    method set_count (line 32) | def set_count(self, count):
    method set_user_id (line 38) | def set_user_id(self, user_id):
    method set_search_term (line 41) | def set_search_term(self, term):
    method parseSearchString (line 70) | def parseSearchString(self, keywords, term):
    method set_search_title (line 118) | def set_search_title(self, term):
    method set_search_artist_name (line 121) | def set_search_artist_name(self, term):
    method set_search_album_title (line 124) | def set_search_album_title(self, term):
    method set_filter_year (line 127) | def set_filter_year(self, term):
    method set_filter_genre (line 130) | def set_filter_genre(self, term):
    method set_filter_album_id (line 133) | def set_filter_album_id(self, term):
    method set_filter_artist_id (line 136) | def set_filter_artist_id(self, term):
    method set_order_by (line 139) | def set_order_by(self, field, direction="asc"):
    method get_default_result (line 147) | def get_default_result(self, result_type, page):
    method result_add_queue_and_favourite (line 185) | def result_add_queue_and_favourite(self, song, dataset):
    method source_set_order (line 204) | def source_set_order(self, object_list):
    method result_set_order (line 220) | def result_set_order(self, result):
  class songs (line 237) | class songs(api_base):
    method index (line 250) | def index(self, page=1):
    method getNextSong (line 350) | def getNextSong(self):
    method getRandomSongByPreferences (line 381) | def getRandomSongByPreferences(self):
    method addToHistory (line 437) | def addToHistory(self, song_instance, user_list):
    method skipCurrentSong (line 447) | def skipCurrentSong(self):
  class history (line 455) | class history(api_base):
    method index (line 468) | def index(self, page=1):
    method build_result (line 475) | def build_result(self, object_list, page):
    method getCurrent (line 539) | def getCurrent(self):
  class history_my (line 583) | class history_my(history):
    method index (line 596) | def index(self, page=1):
  class queue (line 606) | class queue(api_base):
    method index (line 621) | def index(self, page=1):
    method get (line 644) | def get(self, song_id):
    method add (line 693) | def add(self, song_id):
    method remove (line 708) | def remove(self, song_id):
  class favourites (line 724) | class favourites(api_base):
    method index (line 737) | def index(self, page=1):
    method get (line 758) | def get(self, song_id):
    method add (line 801) | def add(self, song_id):
    method remove (line 813) | def remove(self, song_id):
  class artists (line 827) | class artists(api_base):
    method index (line 835) | def index(self, page=1):
  class albums (line 862) | class albums(api_base):
    method index (line 870) | def index(self, page=1):
  class genres (line 897) | class genres(api_base):
    method index (line 905) | def index(self, page=1):
  class years (line 932) | class years(api_base):
    method index (line 940) | def index(self, page=1):
  class players (line 967) | class players(api_base):
    method add (line 968) | def add(self, pid):
    method remove (line 976) | def remove(self, pid):

FILE: jukebox/jukebox_core/forms.py
  class IdForm (line 6) | class IdForm(forms.Form):
  class SongsForm (line 12) | class SongsForm(forms.Form):
  class ArtistsForm (line 58) | class ArtistsForm(forms.Form):
  class AlbumsForm (line 78) | class AlbumsForm(forms.Form):
  class GenresForm (line 98) | class GenresForm(forms.Form):
  class YearsForm (line 118) | class YearsForm(forms.Form):
  class HistoryForm (line 138) | class HistoryForm(forms.Form):
  class FavouritesForm (line 158) | class FavouritesForm(forms.Form):
  class QueueForm (line 178) | class QueueForm(forms.Form):

FILE: jukebox/jukebox_core/management/commands/jukebox_index.py
  class Command (line 8) | class Command(BaseCommand):
    method handle (line 14) | def handle(self, *args, **options):
    method index (line 27) | def index(self, path, verbosity):

FILE: jukebox/jukebox_core/management/commands/jukebox_setup.py
  class Command (line 6) | class Command(BaseCommand):
    method handle (line 7) | def handle(self, *args, **options):
    method setAuthentication (line 27) | def setAuthentication(self):
    method readAppData (line 63) | def readAppData(self, name):
    method setup (line 91) | def setup(self, admin_user, admin_email, authentication):

FILE: jukebox/jukebox_core/migrations/0001_initial.py
  class Migration (line 8) | class Migration(SchemaMigration):
    method forwards (line 10) | def forwards(self, orm):
    method backwards (line 98) | def backwards(self, orm):

FILE: jukebox/jukebox_core/migrations/0002_auto__del_field_album_Artist.py
  class Migration (line 8) | class Migration(SchemaMigration):
    method forwards (line 10) | def forwards(self, orm):
    method backwards (line 15) | def backwards(self, orm):

FILE: jukebox/jukebox_core/models.py
  class Artist (line 8) | class Artist(models.Model):
    class Meta (line 9) | class Meta:
    method __unicode__ (line 12) | def __unicode__(self):
  class Genre (line 18) | class Genre(models.Model):
    class Meta (line 19) | class Meta:
    method __unicode__ (line 22) | def __unicode__(self):
  class Album (line 28) | class Album(models.Model):
    class Meta (line 29) | class Meta:
    method __unicode__ (line 32) | def __unicode__(self):
  class Song (line 38) | class Song(models.Model):
    class Meta (line 39) | class Meta:
    method __unicode__ (line 42) | def __unicode__(self):
  class Queue (line 54) | class Queue(models.Model):
  class Favourite (line 60) | class Favourite(models.Model):
    class Meta (line 61) | class Meta:
  class History (line 70) | class History(models.Model):
    class Meta (line 71) | class Meta:
  class Player (line 79) | class Player(models.Model):
  class QueueFeed (line 83) | class QueueFeed(Feed):
    method items (line 88) | def items(self):
    method item_title (line 91) | def item_title(self, item):
    method item_description (line 95) | def item_description(self, item):
    method item_link (line 101) | def item_link(self, item):

FILE: jukebox/jukebox_core/tests/api.py
  class ApiTestBase (line 10) | class ApiTestBase(TestCase):
    method setUp (line 17) | def setUp(self):
    method httpGet (line 23) | def httpGet(self, url, params={}, user=None):
    method httpPost (line 27) | def httpPost(self, url, params={}, user=None):
    method httpDelete (line 31) | def httpDelete(self, url, params={}, user=None):
    method getAuth (line 35) | def getAuth(self, user=None):
    method addArtist (line 46) | def addArtist(self, name="TestArist"):
    method addAlbum (line 53) | def addAlbum(self, title="TestTitle"):
    method addGenre (line 60) | def addGenre(self, name="TestGenre"):
    method addSong (line 67) | def addSong(
    method addUser (line 90) | def addUser(self, username, email, password):

FILE: jukebox/jukebox_core/tests/api_albums.py
  class ApiAlbumsTest (line 7) | class ApiAlbumsTest(ApiTestBase):
    method testIndexEmpty (line 8) | def testIndexEmpty(self):
    method testIndex (line 17) | def testIndex(self):
    method testIndexOrderByAlbum (line 29) | def testIndexOrderByAlbum(self):
    method testCount (line 53) | def testCount(self):
    method testCountAndPage (line 78) | def testCountAndPage(self):

FILE: jukebox/jukebox_core/tests/api_artists.py
  class ApiArtistsTest (line 7) | class ApiArtistsTest(ApiTestBase):
    method testIndexEmpty (line 8) | def testIndexEmpty(self):
    method testIndex (line 17) | def testIndex(self):
    method testIndexOrderBy (line 29) | def testIndexOrderBy(self):
    method testCount (line 53) | def testCount(self):
    method testCountAndPage (line 78) | def testCountAndPage(self):

FILE: jukebox/jukebox_core/tests/api_favourites.py
  class ApiFavouritesTest (line 8) | class ApiFavouritesTest(ApiTestBase):
    method testIndexEmpty (line 9) | def testIndexEmpty(self):
    method testAddAndIndex (line 17) | def testAddAndIndex(self):
    method testDeleteAndIndex (line 85) | def testDeleteAndIndex(self):
    method addFavourite (line 126) | def addFavourite(self, song):
    method testIndexOrderByTitle (line 132) | def testIndexOrderByTitle(self):
    method testIndexOrderByArtist (line 158) | def testIndexOrderByArtist(self):
    method testIndexOrderByAlbum (line 184) | def testIndexOrderByAlbum(self):
    method testIndexOrderByYear (line 212) | def testIndexOrderByYear(self):
    method testIndexOrderByGenre (line 238) | def testIndexOrderByGenre(self):
    method testIndexOrderByCreated (line 270) | def testIndexOrderByCreated(self):
    method testCount (line 296) | def testCount(self):
    method testCountAndPage (line 324) | def testCountAndPage(self):

FILE: jukebox/jukebox_core/tests/api_genres.py
  class ApiGenresTest (line 7) | class ApiGenresTest(ApiTestBase):
    method testIndexEmpty (line 8) | def testIndexEmpty(self):
    method testIndex (line 17) | def testIndex(self):
    method testIndexOrderBy (line 29) | def testIndexOrderBy(self):
    method testCount (line 53) | def testCount(self):
    method testCountAndPage (line 78) | def testCountAndPage(self):

FILE: jukebox/jukebox_core/tests/api_history.py
  class ApiHistoryTest (line 9) | class ApiHistoryTest(ApiTestBase):
    method testIndexEmpty (line 10) | def testIndexEmpty(self):
    method addSongToQueue (line 18) | def addSongToQueue(self, song, user=None):
    method getNextSong (line 28) | def getNextSong(self):
    method testAddAndIndex (line 32) | def testAddAndIndex(self):
    method testAddAndIndexMy (line 56) | def testAddAndIndexMy(self):
    method testIndexOrderByTitle (line 115) | def testIndexOrderByTitle(self):
    method testIndexOrderByArtist (line 163) | def testIndexOrderByArtist(self):
    method testIndexOrderByAlbum (line 211) | def testIndexOrderByAlbum(self):
    method testIndexOrderByYear (line 264) | def testIndexOrderByYear(self):
    method testIndexOrderByGenre (line 312) | def testIndexOrderByGenre(self):
    method testIndexOrderByCreated (line 364) | def testIndexOrderByCreated(self):
    method testCount (line 414) | def testCount(self):
    method testCountAndPage (line 474) | def testCountAndPage(self):

FILE: jukebox/jukebox_core/tests/api_queue.py
  class ApiQueueTest (line 6) | class ApiQueueTest(ApiTestBase):
    method testIndexEmpty (line 7) | def testIndexEmpty(self):
    method testAddAndIndex (line 15) | def testAddAndIndex(self):
    method testDeleteAndIndex (line 58) | def testDeleteAndIndex(self):
    method addToQueue (line 99) | def addToQueue(self, song):
    method testIndexOrderByTitle (line 105) | def testIndexOrderByTitle(self):
    method testIndexOrderByArtist (line 131) | def testIndexOrderByArtist(self):
    method testIndexOrderByAlbum (line 157) | def testIndexOrderByAlbum(self):
    method testIndexOrderByYear (line 185) | def testIndexOrderByYear(self):
    method testIndexOrderByGenre (line 211) | def testIndexOrderByGenre(self):
    method testIndexOrderByCreated (line 243) | def testIndexOrderByCreated(self):
    method testCount (line 269) | def testCount(self):
    method testCountAndPage (line 297) | def testCountAndPage(self):

FILE: jukebox/jukebox_core/tests/api_songs.py
  class ApiSongsTest (line 8) | class ApiSongsTest(ApiTestBase):
    method testIndexEmpty (line 9) | def testIndexEmpty(self):
    method testIndex (line 18) | def testIndex(self):
    method testIndexWithSearchTermInTitle (line 30) | def testIndexWithSearchTermInTitle(self):
    method testIndexWithSearchTermInArtistName (line 46) | def testIndexWithSearchTermInArtistName(self):
    method testIndexWithSearchTermInAlbumTitle (line 62) | def testIndexWithSearchTermInAlbumTitle(self):
    method testIndexWithSearchTitle (line 83) | def testIndexWithSearchTitle(self):
    method testIndexWithSearchArtistName (line 99) | def testIndexWithSearchArtistName(self):
    method testIndexWithSearchAlbumTitle (line 115) | def testIndexWithSearchAlbumTitle(self):
    method testIndexWithFilterYear (line 136) | def testIndexWithFilterYear(self):
    method testIndexWithFilterGenre (line 150) | def testIndexWithFilterGenre(self):
    method testIndexWithFilterAlbumId (line 164) | def testIndexWithFilterAlbumId(self):
    method testIndexWithFilterArtistId (line 179) | def testIndexWithFilterArtistId(self):
    method testIndexOrderByTitle (line 193) | def testIndexOrderByTitle(self):
    method testIndexOrderByArtist (line 217) | def testIndexOrderByArtist(self):
    method testIndexOrderByAlbum (line 241) | def testIndexOrderByAlbum(self):
    method testIndexOrderByYear (line 268) | def testIndexOrderByYear(self):
    method testIndexOrderByGenre (line 292) | def testIndexOrderByGenre(self):
    method testIndexOrderByLength (line 322) | def testIndexOrderByLength(self):
    method testCount (line 346) | def testCount(self):
    method testCountAndPage (line 371) | def testCountAndPage(self):
    method testGetNextSongRandom (line 403) | def testGetNextSongRandom(self):
    method testGetNextSongFromQueue (line 417) | def testGetNextSongFromQueue(self):

FILE: jukebox/jukebox_core/tests/api_years.py
  class ApiYearsTest (line 7) | class ApiYearsTest(ApiTestBase):
    method testIndexEmpty (line 8) | def testIndexEmpty(self):
    method testIndex (line 17) | def testIndex(self):
    method testIndexOrderBy (line 30) | def testIndexOrderBy(self):
    method testCount (line 56) | def testCount(self):
    method testCountAndPage (line 84) | def testCountAndPage(self):

FILE: jukebox/jukebox_core/utils.py
  class FileIndexer (line 8) | class FileIndexer:
    method index (line 9) | def index(self, filename):
    method delete (line 69) | def delete(self, filename):
    method is_indexed (line 75) | def is_indexed(self, filename):

FILE: jukebox/jukebox_core/views.py
  class JukeboxAPIView (line 13) | class JukeboxAPIView(APIView):
    method api_set_user_id (line 14) | def api_set_user_id(self, request, api):
  class songs (line 20) | class songs(JukeboxAPIView):
    method get (line 23) | def get(self, request):
  class songs_current (line 87) | class songs_current(JukeboxAPIView):
    method get (line 90) | def get(self, request):
  class songs_skip (line 105) | class songs_skip(JukeboxAPIView):
    method get (line 108) | def get(self, request):
  class artists (line 115) | class artists(JukeboxAPIView):
    method get (line 118) | def get(self, request):
  class albums (line 145) | class albums(JukeboxAPIView):
    method get (line 148) | def get(self, request):
  class genres (line 175) | class genres(JukeboxAPIView):
    method get (line 178) | def get(self, request):
  class years (line 205) | class years(JukeboxAPIView):
    method get (line 208) | def get(self, request):
  class history (line 235) | class history(JukeboxAPIView):
    method get (line 238) | def get(self, request):
  class history_my (line 266) | class history_my(JukeboxAPIView):
    method get (line 269) | def get(self, request):
  class queue (line 297) | class queue(JukeboxAPIView):
    method get (line 301) | def get(self, request):
    method post (line 334) | def post(self, request):
  class queue_item (line 359) | class queue_item(JukeboxAPIView):
    method get (line 363) | def get(self, request, song_id):
    method delete (line 384) | def delete(self, request, song_id):
  class favourites (line 400) | class favourites(JukeboxAPIView):
    method get (line 404) | def get(self, request):
    method post (line 437) | def post(self, request):
  class favourites_item (line 462) | class favourites_item(JukeboxAPIView):
    method get (line 466) | def get(self, request, song_id):
    method delete (line 487) | def delete(self, request, song_id):
  class ping (line 504) | class ping(JukeboxAPIView):
    method get (line 505) | def get(self, request):

FILE: jukebox/jukebox_web/views.py
  function index (line 12) | def index(request):
  function login (line 30) | def login(request):
  function login_error (line 42) | def login_error(request):
  function logout (line 50) | def logout(request):
  function language (line 54) | def language(request, language):
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (277K chars).
[
  {
    "path": ".gitignore",
    "chars": 91,
    "preview": ".idea/\ndist/\njukebox.egg-info/\n*.pyc\n*.sqlite\n*.pid\njukebox/settings_local.py\nbuild/\n*.sw*\n"
  },
  {
    "path": "CHANGES.txt",
    "chars": 959,
    "preview": "v0.1.0, 2011-11-17 -- Initial release\nv0.1.1, 2011-11-21 -- Fixed installer bugs, added personal history, added system t"
  },
  {
    "path": "LICENSE.rst",
    "chars": 1091,
    "preview": "The MIT License\n\nCopyright (c) Jens Nistler, http://jensnistler.de\n\nPermission is hereby granted, free of charge, to any"
  },
  {
    "path": "MANIFEST.in",
    "chars": 262,
    "preview": "include CHANGES.txt LICENSE.rst README.rst\nrecursive-include jukebox/jukebox_core/docs *\nrecursive-include jukebox/jukeb"
  },
  {
    "path": "README.rst",
    "chars": 5840,
    "preview": "Unmaintained\n============\n\nThe big time of local mp3 libraries is over. Therefore this project has been abandoned and is"
  },
  {
    "path": "bin/jukebox",
    "chars": 352,
    "preview": "#!/usr/bin/env python\n\nimport imp\nimport sys\n\ntry:\n    path = imp.find_module('jukebox')[1]\n    sys.path.append(path)\nex"
  },
  {
    "path": "jukebox/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "jukebox/jukebox.wsgi",
    "chars": 281,
    "preview": "import os\nimport sys\n\npath = os.path.normpath(os.path.dirname(__file__))\nif path not in sys.path:\n    sys.path.append(pa"
  },
  {
    "path": "jukebox/jukebox_core/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "jukebox/jukebox_core/admin.py",
    "chars": 1098,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom models import Artist, Genre, Album, Song, Queue, History, Favourite\nfrom django.contrib im"
  },
  {
    "path": "jukebox/jukebox_core/api.py",
    "chars": 30480,
    "preview": "# -*- coding: UTF-8 -*-\nfrom django.core.paginator import Paginator, InvalidPage\nfrom django.core.exceptions import Obje"
  },
  {
    "path": "jukebox/jukebox_core/docs/API.rst",
    "chars": 2967,
    "preview": "API\n=====\n\nJukebox core provides a REST API for authenticated users to control the jukebox.\nPlease register a Django Use"
  },
  {
    "path": "jukebox/jukebox_core/forms.py",
    "chars": 4016,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom django import forms\n\n\nclass IdForm(forms.Form):\n    id = forms.IntegerField(\n        requi"
  },
  {
    "path": "jukebox/jukebox_core/management/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "jukebox/jukebox_core/management/commands/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "jukebox/jukebox_core/management/commands/jukebox_index.py",
    "chars": 1277,
    "preview": "# -*- coding: UTF-8 -*-\nfrom django.core.management.base import BaseCommand\nfrom optparse import make_option\nimport os\nf"
  },
  {
    "path": "jukebox/jukebox_core/management/commands/jukebox_setup.py",
    "chars": 5158,
    "preview": "# -*- coding: UTF-8 -*-\nfrom django.core.management.base import BaseCommand\nfrom django.conf import settings\n\n\nclass Com"
  },
  {
    "path": "jukebox/jukebox_core/migrations/0001_initial.py",
    "chars": 12995,
    "preview": "# -*- coding: utf-8 -*-\nimport datetime\nfrom south.db import db\nfrom south.v2 import SchemaMigration\nfrom django.db impo"
  },
  {
    "path": "jukebox/jukebox_core/migrations/0002_auto__del_field_album_Artist.py",
    "chars": 7369,
    "preview": "# -*- coding: utf-8 -*-\nimport datetime\nfrom south.db import db\nfrom south.v2 import SchemaMigration\nfrom django.db impo"
  },
  {
    "path": "jukebox/jukebox_core/migrations/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "jukebox/jukebox_core/models.py",
    "chars": 2500,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom django.db import models\nfrom django.contrib.auth.models import User\nfrom django.contrib.sy"
  },
  {
    "path": "jukebox/jukebox_core/tests/__init__.py",
    "chars": 236,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom .api_songs import *\nfrom .api_artists import *\nfrom .api_albums import *\nfrom .api_genres "
  },
  {
    "path": "jukebox/jukebox_core/tests/api.py",
    "chars": 2356,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom django.test import TestCase, Client\nfrom django.db import transaction\nimport base64\nfrom j"
  },
  {
    "path": "jukebox/jukebox_core/tests/api_albums.py",
    "chars": 3434,
    "preview": "# -*- coding: UTF-8 -*-\n\nimport simplejson\nfrom jukebox.jukebox_core.tests.api import ApiTestBase\n\n\nclass ApiAlbumsTest("
  },
  {
    "path": "jukebox/jukebox_core/tests/api_artists.py",
    "chars": 3437,
    "preview": "# -*- coding: UTF-8 -*-\n\nimport simplejson\nfrom jukebox.jukebox_core.tests.api import ApiTestBase\n\n\nclass ApiArtistsTest"
  },
  {
    "path": "jukebox/jukebox_core/tests/api_favourites.py",
    "chars": 12003,
    "preview": "# -*- coding: UTF-8 -*-\n\nimport simplejson\nfrom jukebox.jukebox_core.tests.api import ApiTestBase\n\n# ATTENTION: order te"
  },
  {
    "path": "jukebox/jukebox_core/tests/api_genres.py",
    "chars": 3395,
    "preview": "# -*- coding: UTF-8 -*-\n\nimport simplejson\nfrom jukebox.jukebox_core.tests.api import ApiTestBase\n\n\nclass ApiGenresTest("
  },
  {
    "path": "jukebox/jukebox_core/tests/api_history.py",
    "chars": 18478,
    "preview": "# -*- coding: UTF-8 -*-\n\nimport simplejson\nfrom jukebox.jukebox_core import api\nfrom jukebox.jukebox_core.tests.api impo"
  },
  {
    "path": "jukebox/jukebox_core/tests/api_queue.py",
    "chars": 10905,
    "preview": "# -*- coding: UTF-8 -*-\n\nimport simplejson\nfrom jukebox.jukebox_core.tests.api import ApiTestBase\n\nclass ApiQueueTest(Ap"
  },
  {
    "path": "jukebox/jukebox_core/tests/api_songs.py",
    "chars": 14765,
    "preview": "# -*- coding: UTF-8 -*-\n\nimport random, simplejson\nfrom jukebox.jukebox_core import api\nfrom jukebox.jukebox_core.tests."
  },
  {
    "path": "jukebox/jukebox_core/tests/api_years.py",
    "chars": 3754,
    "preview": "# -*- coding: UTF-8 -*-\n\nimport simplejson\nfrom jukebox.jukebox_core.tests.api import ApiTestBase\n\n\nclass ApiYearsTest(A"
  },
  {
    "path": "jukebox/jukebox_core/urls.py",
    "chars": 1768,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom django.conf.urls import patterns, url\nimport views\n\nurlpatterns = patterns(\"\",\n    url(\n  "
  },
  {
    "path": "jukebox/jukebox_core/utils.py",
    "chars": 2618,
    "preview": "# -*- coding: UTF-8 -*-\nfrom jukebox.jukebox_core.models import Artist, Album, Song, Genre\nfrom mutagen.easyid3 import E"
  },
  {
    "path": "jukebox/jukebox_core/views.py",
    "chars": 16732,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom django.core.urlresolvers import reverse\nfrom django.core.exceptions import ObjectDoesNotEx"
  },
  {
    "path": "jukebox/jukebox_web/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "jukebox/jukebox_web/locale/de/LC_MESSAGES/django.po",
    "chars": 3169,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
  },
  {
    "path": "jukebox/jukebox_web/locale/de/LC_MESSAGES/djangojs.po",
    "chars": 2762,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
  },
  {
    "path": "jukebox/jukebox_web/locale/en/LC_MESSAGES/django.po",
    "chars": 3024,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
  },
  {
    "path": "jukebox/jukebox_web/locale/en/LC_MESSAGES/djangojs.po",
    "chars": 2656,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
  },
  {
    "path": "jukebox/jukebox_web/locale/fr/LC_MESSAGES/django.po",
    "chars": 3021,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
  },
  {
    "path": "jukebox/jukebox_web/locale/fr/LC_MESSAGES/djangojs.po",
    "chars": 2692,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
  },
  {
    "path": "jukebox/jukebox_web/locale/pt_BR/LC_MESSAGES/django.po",
    "chars": 3084,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
  },
  {
    "path": "jukebox/jukebox_web/locale/pt_BR/LC_MESSAGES/djangojs.po",
    "chars": 2756,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
  },
  {
    "path": "jukebox/jukebox_web/static/css/music.css",
    "chars": 8673,
    "preview": "body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, textarea, p, blockquote, th, td, "
  },
  {
    "path": "jukebox/jukebox_web/static/js/music.js",
    "chars": 39157,
    "preview": "if (typeof gettext != \"function\") {\n    gettext = function(identifier) {\n        return identifier;\n    }\n}\n\nMusic = {\n "
  },
  {
    "path": "jukebox/jukebox_web/templates/index.html",
    "chars": 7377,
    "preview": "{% load i18n %}\n<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n    <head>\n        <title>{% trans \"Democrat"
  },
  {
    "path": "jukebox/jukebox_web/templates/login.html",
    "chars": 1775,
    "preview": "{% load i18n %}\n<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n    <head>\n        <title>{% trans 'Democrat"
  },
  {
    "path": "jukebox/jukebox_web/urls.py",
    "chars": 749,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom django.conf.urls import patterns, url\nfrom jukebox.jukebox_core.models import QueueFeed\nim"
  },
  {
    "path": "jukebox/jukebox_web/views.py",
    "chars": 2037,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom django.shortcuts import render_to_response\nfrom django.core.context_processors import csrf"
  },
  {
    "path": "jukebox/manage.py",
    "chars": 503,
    "preview": "#!/usr/bin/env python\nfrom django.core.management import execute_manager\nimport imp\ntry:\n    imp.find_module('settings')"
  },
  {
    "path": "jukebox/settings.py",
    "chars": 2539,
    "preview": "import os\nimport pkgutil\nimport sys\n\nBASE_DIR = os.path.normpath(os.path.dirname(__file__))\n\nDEBUG = False\nTEMPLATE_DEBU"
  },
  {
    "path": "jukebox/settings_local.example.py",
    "chars": 290,
    "preview": "ADMINS = (\n    (\"[admin_user]\", \"[admin_email]\"),\n)\n\nDEBUG = True\nTEMPLATE_DEBUG = DEBUG\n\nSECRET_KEY = \"yourSecretKey\"\n\n"
  },
  {
    "path": "jukebox/urls.py",
    "chars": 422,
    "preview": "# -*- coding: UTF-8 -*-\n\nfrom django.conf.urls import patterns, include, url\nfrom django.contrib import admin\nadmin.auto"
  },
  {
    "path": "requirements.txt",
    "chars": 113,
    "preview": "Django==1.4.5\nmutagen==1.21\ndjango-social-auth==0.7.20\ndjangorestframework==2.2.1\nsimplejson==3.1.0\nSouth==0.7.6\n"
  },
  {
    "path": "setup.py",
    "chars": 1215,
    "preview": "# -*- coding: UTF-8 -*-\nimport glob\nfrom setuptools import setup, find_packages\n\nsetup(\n    name=\"jukebox\",\n    packages"
  }
]

// ... and 8 more files (download for full content)

About this extraction

This page contains the full source code of the lociii/jukebox GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (252.6 KB), approximately 61.6k tokens, and a symbol index with 244 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!