Initial commit/enabling of the Radio app.

This commit is contained in:
Josh Washburne 2017-12-29 09:56:47 -05:00
parent d985efada9
commit 0d346adee9
12 changed files with 519 additions and 0 deletions

View file

View file

@ -0,0 +1,203 @@
from django.contrib import admin
from django.db import models
from django.forms import TextInput
from django.utils import timezone
from .models import Album, Artist, Game, Song
class ArtistInline(admin.TabularInline):
model = Song.artists.through
verbose_name = 'artist'
verbose_name_plural = 'artists'
extra = 0
@admin.register(Album)
class AlbumAdmin(admin.ModelAdmin):
# Detail List display
list_display = ('title', 'is_enabled', 'is_published')
search_fields = ['title']
actions = ['publish_items']
# Edit Form display
readonly_fields = ('created_date', 'modified_date')
fieldsets = (
('Album Disabling', {
'classes': ('collapse',),
'fields': ('disabled', 'disabled_date', 'disabled_reason')
}),
('Main', {
'fields': ('title', 'published_date')
}),
('Stats', {
'classes': ('collapse',),
'fields': ('created_date', 'modified_date')
})
)
def is_enabled(self, obj):
return not obj.disabled
def is_published(self, obj):
return obj.is_published
def publish_items(self, request, queryset):
rows_updated = queryset.update(published_date=timezone.now())
if rows_updated == 1:
msg = '1 album was'
else:
msg = '{} albums were'.format(str(rows_updated))
self.message_user(request, '{} successfully published.'.format(msg))
publish_items.short_description = "Publish selected items"
@admin.register(Artist)
class ArtistAdmin(admin.ModelAdmin):
# Detail List display
list_display = ('first_name',
'alias',
'last_name',
'is_enabled',
'is_published')
search_fields = ['first_name', 'alias', 'last_name']
actions = ['publish_items']
# Edit Form display
readonly_fields = ('created_date', 'modified_date')
fieldsets = (
('Artist Disabling', {
'classes': ('collapse',),
'fields': ('disabled', 'disabled_date', 'disabled_reason')
}),
('Main', {
'fields': ('first_name', 'alias', 'last_name', 'published_date')
}),
('Stats', {
'classes': ('collapse',),
'fields': ('created_date', 'modified_date')
})
)
def is_enabled(self, obj):
return not obj.disabled
def is_published(self, obj):
return obj.is_published
def publish_items(self, request, queryset):
rows_updated = queryset.update(published_date=timezone.now())
if rows_updated == 1:
msg = '1 artist was'
else:
msg = '{} artists were'.format(str(rows_updated))
self.message_user(request, '{} successfully published.'.format(msg))
publish_items.short_description = "Publish selected items"
@admin.register(Game)
class GameAdmin(admin.ModelAdmin):
# Detail List display
list_display = ('title', 'is_enabled', 'is_published')
search_fields = ['title']
actions = ['publish_items']
# Edit Form display
readonly_fields = ('created_date', 'modified_date')
fieldsets = (
('Game Disabling', {
'classes': ('collapse',),
'fields': ('disabled', 'disabled_date', 'disabled_reason')
}),
('Main', {
'fields': ('title', 'published_date')
}),
('Stats', {
'classes': ('collapse',),
'fields': ('created_date', 'modified_date')
})
)
def is_enabled(self, obj):
return not obj.disabled
def is_published(self, obj):
return obj.is_published
def publish_items(self, request, queryset):
rows_updated = queryset.update(published_date=timezone.now())
if rows_updated == 1:
msg = '1 game was'
else:
msg = '{} games were'.format(str(rows_updated))
self.message_user(request, '{} successfully published.'.format(msg))
publish_items.short_description = "Publish selected items"
@admin.register(Song)
class SongAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TextField: {'widget': TextInput(attrs={'size': 160, })},
}
# Detail List display
list_display = ('title',
'game',
'artist_list',
'is_enabled',
'is_published')
search_fields = ['title']
actions = ['publish_items']
# Edit Form display
exclude = ('artists',)
readonly_fields = ('length',
'last_played',
'num_played',
'created_date',
'modified_date')
fieldsets = (
('Song Disabling', {
'classes': ('collapse',),
'fields': ('disabled', 'disabled_date', 'disabled_reason')
}),
('Main', {
'fields': ('song_type',
'title',
'path',
'published_date')
}),
('Stats', {
'classes': ('collapse',),
'fields': ('created_date',
'modified_date',
'last_played',
'num_played',
'length')
}),
('Album', {
'fields': ('album',)
}),
('Game', {
'fields': ('game',)
})
)
inlines = [ArtistInline]
def artist_list(self, obj):
return ', '.join([a.full_name for a in obj.artists.all()])
def is_enabled(self, obj):
return not obj.disabled
def is_published(self, obj):
return obj.is_published
def publish_items(self, request, queryset):
rows_updated = queryset.update(published_date=timezone.now())
if rows_updated == 1:
msg = '1 song was'
else:
msg = '{} songs were'.format(str(rows_updated))
self.message_user(request, '{} successfully published.'.format(msg))
publish_items.short_description = "Publish selected items"

View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class RadioConfig(AppConfig):
name = 'radio'

View file

@ -0,0 +1,56 @@
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
class Disableable(models.Model):
"""
Mixin for models that can be disabled with a specified reason.
"""
disabled = models.BooleanField(_('disabled state'), default=False)
disabled_date = models.DateTimeField(_('disabled on'),
default=None,
blank=True,
null=True)
disabled_reason = models.TextField(_('reason for disabling'), blank=True)
class Meta:
abstract = True
def disable(self, reason=''):
self.disabled = True
self.disabled_date = timezone.now()
self.disabled_reason = reason
self.save()
def enable(self):
self.disabled = False
self.disabled_date = None
self.disabled_reason = ''
self.save()
class Publishable(models.Model):
"""
Mixin for models that can be published to restrict accessibility before an
appointed date/time.
"""
published_date = models.DateTimeField(_('published for listening'),
default=None,
blank=True,
null=True)
class Meta:
abstract = True
def publish(self, date=None):
if date is None:
date = timezone.now()
self.published_date = date
self.save()
@property
def is_published(self):
if self.published_date is not None:
return self.published_date < timezone.now()
return False

View file

@ -0,0 +1,14 @@
from django.db import models
from .querysets import SongQuerySet
class SongManager(models.Manager):
"""
Custom object manager for filtering out common behaviors for a playlist.
"""
def get_queryset(self):
return SongQuerySet(self.model, using=self._db)
def available(self):
return self.get_queryset().songs().enabled().published()

View file

@ -0,0 +1,89 @@
# Generated by Django 2.0 on 2017-12-29 14:36
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Album',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='added on')),
('modified_date', models.DateTimeField(auto_now=True, verbose_name='last modified')),
('disabled', models.BooleanField(default=False, verbose_name='disabled state')),
('disabled_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='disabled on')),
('disabled_reason', models.TextField(blank=True, verbose_name='reason for disabling')),
('published_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='published for listening')),
('title', models.CharField(max_length=255, unique=True, verbose_name='title')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Artist',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='added on')),
('modified_date', models.DateTimeField(auto_now=True, verbose_name='last modified')),
('disabled', models.BooleanField(default=False, verbose_name='disabled state')),
('disabled_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='disabled on')),
('disabled_reason', models.TextField(blank=True, verbose_name='reason for disabling')),
('published_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='published for listening')),
('alias', models.CharField(blank=True, max_length=127, verbose_name='alias')),
('first_name', models.CharField(blank=True, max_length=127, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=127, verbose_name='last name')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Game',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='added on')),
('modified_date', models.DateTimeField(auto_now=True, verbose_name='last modified')),
('disabled', models.BooleanField(default=False, verbose_name='disabled state')),
('disabled_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='disabled on')),
('disabled_reason', models.TextField(blank=True, verbose_name='reason for disabling')),
('published_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='published for listening')),
('title', models.CharField(max_length=255, unique=True, verbose_name='title')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Song',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='added on')),
('modified_date', models.DateTimeField(auto_now=True, verbose_name='last modified')),
('disabled', models.BooleanField(default=False, verbose_name='disabled state')),
('disabled_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='disabled on')),
('disabled_reason', models.TextField(blank=True, verbose_name='reason for disabling')),
('published_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='published for listening')),
('song_type', models.CharField(choices=[('J', 'Jingle'), ('S', 'Song')], default='S', max_length=1, verbose_name='song type')),
('title', models.CharField(max_length=255, verbose_name='title')),
('num_played', models.PositiveIntegerField(default=0, verbose_name='number of times played')),
('last_played', models.DateTimeField(blank=True, editable=False, null=True, verbose_name='was last played')),
('length', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='song length (in seconds)')),
('path', models.TextField(verbose_name='absolute path to song file')),
('album', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='radio.Album')),
('artists', models.ManyToManyField(to='radio.Artist')),
('game', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='radio.Game')),
],
options={
'abstract': False,
},
),
]

View file

@ -0,0 +1,100 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from core.behaviors import Timestampable
from .behaviors import Disableable, Publishable
from .managers import SongManager
class Album(Disableable, Publishable, Timestampable, models.Model):
"""
A model for a music album.
"""
title = models.CharField(_('title'), max_length=255, unique=True)
def __str__(self):
return self.title
class Artist(Disableable, Publishable, Timestampable, models.Model):
"""
A model for a music artist.
"""
alias = models.CharField(_('alias'), max_length=127, blank=True)
first_name = models.CharField(_('first name'), max_length=127, blank=True)
last_name = models.CharField(_('last name'), max_length=127, blank=True)
@property
def full_name(self):
if not self.alias:
return '{} {}'.format(self.first_name, self.last_name)
else:
if not self.first_name or not self.last_name:
return self.alias
else:
return '{} "{}" {}'.format(self.first_name,
self.alias,
self.last_name)
def __str__(self):
return self.full_name
class Game(Disableable, Publishable, Timestampable, models.Model):
"""
A model for a game.
"""
title = models.CharField(_('title'), max_length=255, unique=True)
def __str__(self):
return self.title
class Song(Disableable, Publishable, Timestampable, models.Model):
"""
A model for a song.
"""
JINGLE = 'J'
SONG = 'S'
TYPE_CHOICES = (
(JINGLE, 'Jingle'),
(SONG, 'Song'),
)
album = models.ForeignKey(Album,
on_delete=models.SET_NULL,
null=True,
blank=True)
artists = models.ManyToManyField(Artist)
game = models.ForeignKey(Game,
on_delete=models.SET_NULL,
null=True,
blank=True)
song_type = models.CharField(_('song type'),
max_length=1,
choices=TYPE_CHOICES,
default=SONG)
title = models.CharField(_('title'), max_length=255)
num_played = models.PositiveIntegerField(_('number of times played'),
default=0)
last_played = models.DateTimeField(_('was last played'),
null=True,
blank=True,
editable=False)
length = models.DecimalField(_('song length (in seconds)'),
max_digits=8,
decimal_places=2,
null=True,
blank=True)
path = models.TextField(_('absolute path to song file'))
objects = models.Manager()
music = SongManager()
def __str__(self):
if self.song_type == 'J':
return self.title
else:
all_artists = ', '.join([a.full_name for a in self.artists.all()])
return '{} - {} ({})'.format(self.game.title,
self.title,
all_artists)

View file

@ -0,0 +1,45 @@
from datetime import timedelta
from django.db import models
from django.utils import timezone
class EnabledQuerySet(models.QuerySet):
"""
Queryset to select all objects that are not disabled.
"""
def enabled(self):
return self.filter(disabled=False)
class PublishedQuerySet(models.QuerySet):
"""
Queryset to select all objects that have been published.
"""
def published(self):
results = self.filter(
models.Q(published_date__isnull=False) &
models.Q(published_date__lte=timezone.now())
)
return results
class TypeQuerySet(models.QuerySet):
"""
Queryset to select all objects that are either songs or jingles.
"""
def songs(self):
return self.filter(song_type='S')
def jingles(self):
return self.filter(song_type='J')
class SongQuerySet(EnabledQuerySet,
PublishedQuerySet,
TypeQuerySet):
"""
Queryset combination that can easily select enabled objects, published
objects, and objects of a certain song type.
"""
pass

View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -40,6 +40,7 @@ INSTALLED_APPS = [
'authtools',
'core.apps.CoreConfig',
'radio.apps.RadioConfig',
]
LANGUAGE_CODE = 'en-us'