from django.contrib import admin, messages from django.db import models from django.forms import TextInput from inline_actions.admin import ( InlineActionsMixin, InlineActionsModelAdminMixin ) 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 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 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', 'album', 'artist_list', '_is_enabled', '_is_published', '_is_requestable' ) fields = readonly_fields verbose_name = 'related song' 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 title.short_description = 'title' def game(self, obj): return obj.song.game game.short_description = 'game' def album(self, obj): return obj.song.album album.short_description = 'album' def artist_list(self, obj): return ', '.join([a.full_name for a in obj.song.artists.all()]) def _is_enabled(self, obj): return obj.song.is_enabled _is_enabled.boolean = True def _is_published(self, obj): return obj.song.is_published _is_published.boolean = True def _is_requestable(self, obj): return obj.song.is_requestable _is_requestable.boolean = True @admin.register(Album) class AlbumAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): # Detail List display list_display = ('title', '_is_enabled', '_is_published') search_fields = ['title'] actions = ['publish_albums'] inlines = [SongInline] # 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 publish_albums(self, request, queryset): publish_items(request, queryset) publish_albums.short_description = "Publish selected albums" class Media: css = { 'all': ('css/remove_inline_object_names.css', ) } @admin.register(Artist) class ArtistAdmin(InlineActionsModelAdminMixin, 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_artists'] inlines = [SongArtistInline] # 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 publish_artists(self, request, queryset): publish_items(request, queryset) publish_artists.short_description = "Publish selected artists" class Media: css = { 'all': ('css/remove_inline_object_names.css', ) } @admin.register(Game) class GameAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): # Detail List display list_display = ('title', '_is_enabled', '_is_published') search_fields = ['title'] actions = ['publish_games'] inlines = [SongInline] # 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 publish_games(self, request, queryset): publish_items(request, queryset) publish_games.short_description = "Publish selected games" class Media: css = { 'all': ('css/remove_inline_object_names.css', ) } @admin.register(Store) class StoreAdmin(admin.ModelAdmin): # Detail List display list_display = ('iri', 'mime_type', 'file_size', 'length', '_replaygain') search_fields = ['iri'] # Edit Form display readonly_fields = ('created_date', 'modified_date') fieldsets = ( ('Main', { 'fields': ('iri', 'mime_type', 'file_size', 'length', 'track_gain', 'track_peak') }), ('Stats', { 'classes': ('collapse',), 'fields': ('created_date', 'modified_date') }) ) @admin.register(Song) class SongAdmin(RequestSongActionMixin, InlineActionsModelAdminMixin, admin.ModelAdmin): formfield_overrides = { models.TextField: {'widget': TextInput(attrs={'size': 160, })}, } # Detail List display list_display = ('title', 'game', 'album', 'artist_list', '_is_enabled', '_is_published', '_is_requestable') list_select_related = True search_fields = ['title'] actions = ['publish_songs', 'add_game', 'remove_game', 'add_album', 'remove_album', 'add_artists', 'remove_artists'] # Edit Form display exclude = ('artists',) readonly_fields = ('last_played', 'num_played', 'created_date', 'modified_date', 'next_play') fieldsets = ( ('Song Disabling', { 'classes': ('collapse',), 'fields': ('disabled', 'disabled_date', 'disabled_reason') }), ('Main', { 'fields': ('song_type', 'title', 'published_date', 'active_store') }), ('Stats', { 'classes': ('collapse',), 'fields': ('created_date', 'modified_date', 'last_played', 'num_played', 'next_play') }), ('Album', { 'fields': ('album',) }), ('Game', { 'fields': ('game',) }) ) inlines = [ArtistInline, StoreInline] def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'active_store': kwargs['queryset'] = Store.objects.filter( song__pk=request.resolver_match.kwargs['object_id'] ) return super().formfield_for_foreignkey(db_field, request, **kwargs) # Admin Actions def artist_list(self, obj): return ', '.join([a.full_name for a in obj.artists.all()]) def add_album(self, request, queryset): return change_items(request, queryset, 'album', 'add_album') add_album.short_description = "Add album to selected items" def remove_album(self, request, queryset): return remove_items(request, queryset, 'album', 'remove_album') remove_album.short_description = "Remove album from selected items" def add_artists(self, request, queryset): return change_items(request, queryset, 'artists', 'add_artists', m2m='artist') add_artists.short_description = "Add artists to selected items" def remove_artists(self, request, queryset): return change_items(request, queryset, 'artists', 'remove_artists', m2m='artist', remove=True) remove_artists.short_description = "Remove artists from selected items" def add_game(self, request, queryset): return change_items(request, queryset, 'game', 'add_game') add_game.short_description = "Add game to selected items" def remove_game(self, request, queryset): return remove_items(request, queryset, 'game', 'remove_game') remove_game.short_description = "Remove game from selected items" def publish_songs(self, request, queryset): publish_items(request, queryset) publish_songs.short_description = "Publish selected songs" class Media: css = { 'all': ('css/remove_inline_object_names.css', ) }