Initial commit/enabling of the Radio app.
This commit is contained in:
parent
d985efada9
commit
0d346adee9
12 changed files with 519 additions and 0 deletions
0
savepointradio/radio/__init__.py
Normal file
0
savepointradio/radio/__init__.py
Normal file
203
savepointradio/radio/admin.py
Normal file
203
savepointradio/radio/admin.py
Normal 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"
|
5
savepointradio/radio/apps.py
Normal file
5
savepointradio/radio/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RadioConfig(AppConfig):
|
||||
name = 'radio'
|
56
savepointradio/radio/behaviors.py
Normal file
56
savepointradio/radio/behaviors.py
Normal 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
|
14
savepointradio/radio/managers.py
Normal file
14
savepointradio/radio/managers.py
Normal 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()
|
89
savepointradio/radio/migrations/0001_initial.py
Normal file
89
savepointradio/radio/migrations/0001_initial.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
0
savepointradio/radio/migrations/__init__.py
Normal file
0
savepointradio/radio/migrations/__init__.py
Normal file
100
savepointradio/radio/models.py
Normal file
100
savepointradio/radio/models.py
Normal 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)
|
45
savepointradio/radio/querysets.py
Normal file
45
savepointradio/radio/querysets.py
Normal 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
|
3
savepointradio/radio/tests.py
Normal file
3
savepointradio/radio/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
savepointradio/radio/views.py
Normal file
3
savepointradio/radio/views.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
|
@ -40,6 +40,7 @@ INSTALLED_APPS = [
|
|||
'authtools',
|
||||
|
||||
'core.apps.CoreConfig',
|
||||
'radio.apps.RadioConfig',
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
|
Loading…
Reference in a new issue