diff --git a/savepointradio/profiles/actions.py b/savepointradio/profiles/actions.py new file mode 100644 index 0000000..b52da5a --- /dev/null +++ b/savepointradio/profiles/actions.py @@ -0,0 +1,61 @@ +from django.contrib import messages + +from radio.models import Song +from .exceptions import MakeRequestError +from .models import RadioProfile + + +class RequestSongActionMixin(object): + '''This allows a song to be requested directly from an admin page.''' + def get_inline_actions(self, request, obj=None): + actions = super().get_inline_actions(request=request, obj=obj) + actions.append('request_song') + return actions + + def request_song(self, request, obj, parent_obj=None): + profile = RadioProfile.objects.get(user=request.user) + + # This is to get around the M2M 'through' table on Artists + song = obj + if not isinstance(song, Song): + song = obj.song + + try: + profile.make_request(song) + except MakeRequestError as e: + return messages.error( + request, + 'Unable to request the song: {}'.format(str(e)) + ) + return messages.success(request, 'Successfully queued song.') + + def get_request_song_label(self, obj): + return 'Request Song' + + +class ToggleFavoriteActionsMixin(object): + '''This allows a song to be [un]favorited directly from the admin page.''' + def get_inline_actions(self, request, obj=None): + actions = super().get_inline_actions(request=request, obj=obj) + actions.append('toggle_favorite') + return actions + + def toggle_favorite(self, request, obj, parent_obj=None): + profile = RadioProfile.objects.get(user=request.user) + + # This is to get around the M2M 'through' table + song = obj + if not isinstance(song, Song): + song = obj.song + + found = profile.favorites.filter(pk=song.pk).exists() + if found: + profile.favorites.remove(song) + else: + profile.favorites.add(song) + + status = 'removed from favorites' if found else 'added to favorites' + messages.success(request, 'Song successfully {}.'.format(status)) + + def get_toggle_favorite_label(self, obj): + return 'Toggle Favorite' diff --git a/savepointradio/profiles/admin.py b/savepointradio/profiles/admin.py index 2dff160..82c70c0 100644 --- a/savepointradio/profiles/admin.py +++ b/savepointradio/profiles/admin.py @@ -1,13 +1,15 @@ from django.contrib import admin +from inline_actions.admin import InlineActionsModelAdminMixin + +from radio.admin import BaseSongInline from .models import RadioProfile, SongRequest -class FavoriteInline(admin.TabularInline): +class FavoriteInline(BaseSongInline): model = RadioProfile.favorites.through verbose_name = 'favorite' verbose_name_plural = 'favorites' - extra = 0 class RatingInline(admin.TabularInline): @@ -18,7 +20,7 @@ class RatingInline(admin.TabularInline): @admin.register(RadioProfile) -class ProfileAdmin(admin.ModelAdmin): +class ProfileAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): # Edit Form display readonly_fields = ('created_date', 'modified_date') fieldsets = ( @@ -37,6 +39,11 @@ class ProfileAdmin(admin.ModelAdmin): inlines = [FavoriteInline, RatingInline] + class Media: + css = { + 'all': ('css/remove_inline_object_names.css', ) + } + @admin.register(SongRequest) class RequestAdmin(admin.ModelAdmin): diff --git a/savepointradio/radio/admin.py b/savepointradio/radio/admin.py index 6fda859..a16c0e8 100644 --- a/savepointradio/radio/admin.py +++ b/savepointradio/radio/admin.py @@ -8,56 +8,20 @@ from inline_actions.admin import ( from .actions import change_items, publish_items, remove_items from .models import Album, Artist, Game, Song, Store -from profiles.exceptions import MakeRequestError -from profiles.models import RadioProfile +from profiles.actions import RequestSongActionMixin, ToggleFavoriteActionsMixin -class RequestSongActionMixin(object): - '''This allows a song to be requested directly from an admin page.''' - def get_inline_actions(self, request, obj=None): - actions = super().get_inline_actions(request=request, obj=obj) - actions.append('request_song') - return actions +class BaseSongInline(RequestSongActionMixin, + ToggleFavoriteActionsMixin, + InlineActionsMixin, + admin.TabularInline): + # This is VERY hacky and I hate it. I would love to find something + # a little more DRY. There's this: + # https://gist.github.com/cauethenorio/9db40c59cf406bf328fd + # . . .but it seems to be aimed at ModelAdmins, not Inlines. Maybe a + # refactor is in order when I understand the underlying functionality + # down the road. - def request_song(self, request, obj, parent_obj=None): - profile = RadioProfile.objects.get(user=request.user) - - # This is to get around the M2M 'through' table on Artists - song = obj - if not isinstance(song, Song): - song = obj.song - - try: - profile.make_request(song) - except MakeRequestError as e: - return messages.error( - request, - 'Unable to request the song: {}'.format(str(e)) - ) - return messages.success(request, 'Successfully queued song.') - - def get_request_song_label(self, obj): - return 'Request Song' - - -class ArtistInline(admin.TabularInline): - model = Song.artists.through - verbose_name = 'artist' - verbose_name_plural = 'artists' - extra = 0 - - -class StoreInline(admin.TabularInline): - model = Song.stores.through - verbose_name = 'data store' - verbose_name_plural = 'data stores' - extra = 0 - - -class SongInline(RequestSongActionMixin, - InlineActionsMixin, - admin.TabularInline): - model = Song readonly_fields = ( 'title', 'game', @@ -72,47 +36,77 @@ class SongInline(RequestSongActionMixin, verbose_name_plural = 'related songs' extra = 0 - def artist_list(self, obj): - return ', '.join([a.full_name for a in obj.artists.all()]) - -class SongArtistInline(SongInline): - model = Song.artists.through - - # This is VERY hacky and I hate it. I would love to find something - # a little more DRY. There's this: - # https://gist.github.com/cauethenorio/9db40c59cf406bf328fd - # . . .but it seems to be aimed at ModelAdmins, not Inlines. Maybe a - # refactor is in order when I understand the underlying functionality - # down the road. - def title(self, obj): - return obj.song.title + song = obj + if not isinstance(song, Song): + song = obj.song + return song.title title.short_description = 'title' def game(self, obj): - return obj.song.game + song = obj + if not isinstance(song, Song): + song = obj.song + return song.game game.short_description = 'game' def album(self, obj): - return obj.song.album + song = obj + if not isinstance(song, Song): + song = obj.song + return song.album album.short_description = 'album' def artist_list(self, obj): - return ', '.join([a.full_name for a in obj.song.artists.all()]) + song = obj + if not isinstance(song, Song): + song = obj.song + return ', '.join([a.full_name for a in song.artists.all()]) def _is_enabled(self, obj): + song = obj + if not isinstance(song, Song): + song = obj.song return obj.song.is_enabled _is_enabled.boolean = True def _is_published(self, obj): + song = obj + if not isinstance(song, Song): + song = obj.song return obj.song.is_published _is_published.boolean = True def _is_requestable(self, obj): + song = obj + if not isinstance(song, Song): + song = obj.song return obj.song.is_requestable _is_requestable.boolean = True +class SongInline(BaseSongInline): + model = Song + + +class SongArtistInline(BaseSongInline): + model = Song.artists.through + + +class ArtistInline(admin.TabularInline): + model = Song.artists.through + verbose_name = 'artist' + verbose_name_plural = 'artists' + extra = 0 + + +class StoreInline(admin.TabularInline): + model = Song.stores.through + verbose_name = 'data store' + verbose_name_plural = 'data stores' + extra = 0 + + @admin.register(Album) class AlbumAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): # Detail List display @@ -249,6 +243,7 @@ class StoreAdmin(admin.ModelAdmin): @admin.register(Song) class SongAdmin(RequestSongActionMixin, + ToggleFavoriteActionsMixin, InlineActionsModelAdminMixin, admin.ModelAdmin): formfield_overrides = {