update tracks, work on admin, fix stuff and models
This commit is contained in:
parent
b36c2f944e
commit
682d8b9189
|
@ -1,11 +1,11 @@
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.forms import Textarea
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# import autocomplete_light as al
|
from suit.admin import SortableTabularInline
|
||||||
|
|
||||||
|
from programs.forms import *
|
||||||
from programs.models import *
|
from programs.models import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,13 +13,6 @@ from programs.models import *
|
||||||
# Inlines
|
# Inlines
|
||||||
#
|
#
|
||||||
# TODO: inherits from the corresponding admin view
|
# TODO: inherits from the corresponding admin view
|
||||||
class SoundInline (admin.TabularInline):
|
|
||||||
model = Sound
|
|
||||||
raw_id_fields=('parent',)
|
|
||||||
fields = ('title', 'private', 'tags', 'file', 'duration', 'fragment')
|
|
||||||
extra = 1
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleInline (admin.TabularInline):
|
class ScheduleInline (admin.TabularInline):
|
||||||
model = Schedule
|
model = Schedule
|
||||||
extra = 1
|
extra = 1
|
||||||
|
@ -27,11 +20,20 @@ class ScheduleInline (admin.TabularInline):
|
||||||
|
|
||||||
class DiffusionInline (admin.TabularInline):
|
class DiffusionInline (admin.TabularInline):
|
||||||
model = Diffusion
|
model = Diffusion
|
||||||
raw_id_fields=('parent',)
|
fields = ('episode', 'type', 'begin', 'end', 'stream')
|
||||||
fields = ('parent', 'type', 'date')
|
readonly_fields = ('begin', 'end', 'stream')
|
||||||
extra = 1
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TrackInline (SortableTabularInline):
|
||||||
|
fields = ['artist', 'title', 'tags', 'position']
|
||||||
|
form = TrackForm
|
||||||
|
model = Track
|
||||||
|
sortable = 'position'
|
||||||
|
extra = 10
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Parents
|
# Parents
|
||||||
#
|
#
|
||||||
|
@ -41,7 +43,7 @@ class MetadataAdmin (admin.ModelAdmin):
|
||||||
'fields': [ 'title', 'tags' ]
|
'fields': [ 'title', 'tags' ]
|
||||||
}),
|
}),
|
||||||
( None, {
|
( None, {
|
||||||
'fields': [ 'date' ],
|
'fields': [ 'date', 'public', 'enumerable' ],
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -52,17 +54,19 @@ class MetadataAdmin (admin.ModelAdmin):
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
from autocomplete_light.contrib.taggit_field import TaggitWidget, TaggitField
|
||||||
class PublicationAdmin (MetadataAdmin):
|
class PublicationAdmin (MetadataAdmin):
|
||||||
fieldsets = copy.deepcopy(MetadataAdmin.fieldsets)
|
fieldsets = copy.deepcopy(MetadataAdmin.fieldsets)
|
||||||
|
|
||||||
list_display = ('id', 'title', 'date', 'private')
|
list_display = ('id', 'title', 'date', 'public', 'parent')
|
||||||
list_filter = ['date', 'private']
|
list_filter = ['date', 'public', 'parent', 'author']
|
||||||
search_fields = ['title', 'content']
|
search_fields = ['title', 'content']
|
||||||
|
|
||||||
|
|
||||||
fieldsets[0][1]['fields'].insert(1, 'subtitle')
|
fieldsets[0][1]['fields'].insert(1, 'subtitle')
|
||||||
fieldsets[0][1]['fields'] += [ 'img', 'content' ]
|
fieldsets[0][1]['fields'] += [ 'img', 'content' ]
|
||||||
fieldsets[1][1]['fields'] += [ 'parent', 'private', 'can_comment' ] #, 'meta' ],
|
fieldsets[1][1]['fields'] += [ 'parent' ] #, 'meta' ],
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -90,17 +94,19 @@ class ProgramAdmin (PublicationAdmin):
|
||||||
|
|
||||||
class EpisodeAdmin (PublicationAdmin):
|
class EpisodeAdmin (PublicationAdmin):
|
||||||
fieldsets = copy.deepcopy(PublicationAdmin.fieldsets)
|
fieldsets = copy.deepcopy(PublicationAdmin.fieldsets)
|
||||||
#inlines = [ SoundInline ]
|
|
||||||
list_filter = ['parent'] + PublicationAdmin.list_filter
|
list_filter = ['parent'] + PublicationAdmin.list_filter
|
||||||
|
|
||||||
# FIXME later: when we have thousands of tracks
|
|
||||||
fieldsets[0][1]['fields'] += ['tracks']
|
|
||||||
fieldsets[0][1]['fields'] += ['sounds']
|
fieldsets[0][1]['fields'] += ['sounds']
|
||||||
|
|
||||||
raw_id_fields = ('tracks', 'sounds')
|
inlines = (TrackInline, DiffusionInline)
|
||||||
autocomplete_lookup_fields = {
|
|
||||||
'm2m': ['tracks', 'sounds'],
|
|
||||||
}
|
class DiffusionAdmin (admin.ModelAdmin):
|
||||||
|
list_display = ('type', 'begin', 'end', 'episode', 'program', 'stream')
|
||||||
|
list_filter = ('type', 'begin', 'program', 'stream')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Track)
|
admin.site.register(Track)
|
||||||
admin.site.register(Sound, SoundAdmin)
|
admin.site.register(Sound, SoundAdmin)
|
||||||
|
@ -108,5 +114,5 @@ admin.site.register(Schedule)
|
||||||
admin.site.register(Article, ArticleAdmin)
|
admin.site.register(Article, ArticleAdmin)
|
||||||
admin.site.register(Program, ProgramAdmin)
|
admin.site.register(Program, ProgramAdmin)
|
||||||
admin.site.register(Episode, EpisodeAdmin)
|
admin.site.register(Episode, EpisodeAdmin)
|
||||||
admin.site.register(Diffusion)
|
admin.site.register(Diffusion, DiffusionAdmin)
|
||||||
|
|
||||||
|
|
50
programs/autocomplete_light_registry.py
Normal file
50
programs/autocomplete_light_registry.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import autocomplete_light.shortcuts as al
|
||||||
|
from programs.models import *
|
||||||
|
|
||||||
|
from taggit.models import Tag
|
||||||
|
al.register(Tag)
|
||||||
|
|
||||||
|
|
||||||
|
class OneFieldAutocomplete(al.AutocompleteModelBase):
|
||||||
|
choice_html_format = u'''
|
||||||
|
<span class="block" data-value="%s">%s</span>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def choice_html (self, choice):
|
||||||
|
value = choice[self.search_fields[0]]
|
||||||
|
return self.choice_html_format % (self.choice_label(choice),
|
||||||
|
self.choice_label(value))
|
||||||
|
|
||||||
|
|
||||||
|
def choices_for_request(self):
|
||||||
|
#if not self.request.user.is_staff:
|
||||||
|
# self.choices = self.choices.filter(private=False)
|
||||||
|
filter_args = { self.search_fields[0] + '__icontains': self.request.GET['q'] }
|
||||||
|
|
||||||
|
self.choices = self.choices.filter(**filter_args)
|
||||||
|
self.choices = self.choices.values(self.search_fields[0]).distinct()
|
||||||
|
return self.choices
|
||||||
|
|
||||||
|
|
||||||
|
class TrackArtistAutocomplete(OneFieldAutocomplete):
|
||||||
|
search_fields = ['artist']
|
||||||
|
model = Track
|
||||||
|
|
||||||
|
al.register(TrackArtistAutocomplete)
|
||||||
|
|
||||||
|
|
||||||
|
class TrackTitleAutocomplete(OneFieldAutocomplete):
|
||||||
|
search_fields = ['title']
|
||||||
|
model = Track
|
||||||
|
|
||||||
|
|
||||||
|
al.register(TrackTitleAutocomplete)
|
||||||
|
|
||||||
|
|
||||||
|
#class DiffusionAutocomplete(OneFieldAutocomplete):
|
||||||
|
# search_fields = ['episode', 'program', 'start', 'stop']
|
||||||
|
# model = Diffusion
|
||||||
|
#
|
||||||
|
#al.register(DiffusionAutocomplete)
|
||||||
|
|
||||||
|
|
19
programs/forms.py
Normal file
19
programs/forms.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.admin import widgets
|
||||||
|
|
||||||
|
import autocomplete_light.shortcuts as al
|
||||||
|
from autocomplete_light.contrib.taggit_field import TaggitWidget
|
||||||
|
|
||||||
|
from programs.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class TrackForm (forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Track
|
||||||
|
fields = ['artist', 'title', 'tags', 'position']
|
||||||
|
widgets = {
|
||||||
|
'artist': al.TextWidget('TrackArtistAutocomplete'),
|
||||||
|
'title': al.TextWidget('TrackTitleAutocomplete'),
|
||||||
|
'tags': TaggitWidget('TagAutocomplete'),
|
||||||
|
}
|
||||||
|
|
|
@ -50,9 +50,9 @@ ugettext_lazy('one on two')
|
||||||
|
|
||||||
DiffusionType = {
|
DiffusionType = {
|
||||||
'diffuse': 0x01 # the diffusion is planified or done
|
'diffuse': 0x01 # the diffusion is planified or done
|
||||||
|
, 'scheduled': 0x02 # the diffusion been scheduled automatically
|
||||||
, 'cancel': 0x03 # the diffusion has been canceled from grid; useful to give
|
, 'cancel': 0x03 # the diffusion has been canceled from grid; useful to give
|
||||||
# the info to the users
|
# the info to the users
|
||||||
, 'stop': 0x04 # the diffusion been arbitrary stopped (non-stop or not)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,10 +107,15 @@ class Metadata (Model):
|
||||||
_('date')
|
_('date')
|
||||||
, default = timezone.datetime.now
|
, default = timezone.datetime.now
|
||||||
)
|
)
|
||||||
private = models.BooleanField(
|
public = models.BooleanField(
|
||||||
_('private')
|
_('public')
|
||||||
, default = False
|
, default = True
|
||||||
, help_text = _('publication is private')
|
, help_text = _('publication is public')
|
||||||
|
)
|
||||||
|
enumerable = models.BooleanField(
|
||||||
|
_('enumerable')
|
||||||
|
, default = True
|
||||||
|
, help_text = _('publication is listable')
|
||||||
)
|
)
|
||||||
tags = TaggableManager(
|
tags = TaggableManager(
|
||||||
_('tags')
|
_('tags')
|
||||||
|
@ -143,7 +148,7 @@ class Publication (Metadata):
|
||||||
_('content')
|
_('content')
|
||||||
, blank = True
|
, blank = True
|
||||||
)
|
)
|
||||||
can_comment = models.BooleanField(
|
commentable = models.BooleanField(
|
||||||
_('enable comments')
|
_('enable comments')
|
||||||
, default = True
|
, default = True
|
||||||
, help_text = _('comments are enabled on this publication')
|
, help_text = _('comments are enabled on this publication')
|
||||||
|
@ -209,32 +214,31 @@ class Publication (Metadata):
|
||||||
# Usable models
|
# Usable models
|
||||||
#
|
#
|
||||||
class Track (Model):
|
class Track (Model):
|
||||||
|
# There are no nice solution for M2M relations ship (even without
|
||||||
|
# through) in django-admin. So we unfortunately need to make one-
|
||||||
|
# to-one relations and add a position argument
|
||||||
|
episode = models.ForeignKey(
|
||||||
|
'Episode'
|
||||||
|
, null = True
|
||||||
|
)
|
||||||
artist = models.CharField(
|
artist = models.CharField(
|
||||||
_('artist')
|
_('artist')
|
||||||
, max_length = 128
|
, max_length = 128
|
||||||
, blank = True
|
|
||||||
)
|
)
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
_('title')
|
_('title')
|
||||||
, max_length = 128
|
, max_length = 128
|
||||||
)
|
)
|
||||||
version = models.CharField(
|
|
||||||
_('version')
|
|
||||||
, max_length = 128
|
|
||||||
, blank = True
|
|
||||||
, help_text = _('additional informations on that track')
|
|
||||||
)
|
|
||||||
tags = TaggableManager( blank = True )
|
tags = TaggableManager( blank = True )
|
||||||
|
# position can be used to specify a position in seconds
|
||||||
|
position = models.SmallIntegerField(
|
||||||
@staticmethod
|
default = 0
|
||||||
def autocomplete_search_fields():
|
, help_text=_('position in the playlist')
|
||||||
return ("artist__icontains", 'title__icontains')
|
)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ' '.join([self.artist, ':', self.title,
|
return ' '.join([self.artist, ':', self.title])
|
||||||
(self.version and ('(' + self.version + ')') or '') ])
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -532,12 +536,14 @@ class Program (Publication):
|
||||||
, null = True
|
, null = True
|
||||||
, help_text = _('parent article')
|
, help_text = _('parent article')
|
||||||
)
|
)
|
||||||
|
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
_('email')
|
_('email')
|
||||||
, max_length = 128
|
, max_length = 128
|
||||||
, null = True
|
, null = True
|
||||||
, blank = True
|
, blank = True
|
||||||
)
|
)
|
||||||
|
|
||||||
url = models.URLField(
|
url = models.URLField(
|
||||||
_('website')
|
_('website')
|
||||||
, blank = True
|
, blank = True
|
||||||
|
@ -567,6 +573,7 @@ class Program (Publication):
|
||||||
verbose_name_plural = _('Programs')
|
verbose_name_plural = _('Programs')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Episode (Publication):
|
class Episode (Publication):
|
||||||
# Note:
|
# Note:
|
||||||
# We do not especially need a duration here, because even if an
|
# We do not especially need a duration here, because even if an
|
||||||
|
@ -581,11 +588,6 @@ class Episode (Publication):
|
||||||
, verbose_name = _('parent')
|
, verbose_name = _('parent')
|
||||||
, help_text = _('parent program')
|
, help_text = _('parent program')
|
||||||
)
|
)
|
||||||
tracks = models.ManyToManyField(
|
|
||||||
Track
|
|
||||||
, blank = True
|
|
||||||
, verbose_name = _('tracks')
|
|
||||||
)
|
|
||||||
sounds = models.ManyToManyField(
|
sounds = models.ManyToManyField(
|
||||||
Sound
|
Sound
|
||||||
, blank = True
|
, blank = True
|
||||||
|
@ -620,23 +622,24 @@ class Diffusion (Model):
|
||||||
verbose_name = _('type')
|
verbose_name = _('type')
|
||||||
, choices = [ (y, x) for x,y in DiffusionType.items() ]
|
, choices = [ (y, x) for x,y in DiffusionType.items() ]
|
||||||
)
|
)
|
||||||
date = models.DateTimeField( _('date of diffusion start') )
|
begin = models.DateTimeField( _('start of diffusion start') )
|
||||||
|
end = models.DateTimeField( _('stop of diffusion stop') )
|
||||||
stream = models.SmallIntegerField(
|
stream = models.SmallIntegerField(
|
||||||
verbose_name = _('stream')
|
verbose_name = _('stream')
|
||||||
, default = 0
|
, default = 0
|
||||||
, help_text = 'stream id on which the diffusion happens'
|
, help_text = 'stream id on which the diffusion happens'
|
||||||
)
|
)
|
||||||
scheduled = models.BooleanField(
|
|
||||||
verbose_name = _('scheduled')
|
|
||||||
, default = False
|
|
||||||
, help_text = 'diffusion generated automatically'
|
|
||||||
)
|
|
||||||
|
|
||||||
def save (self, *args, **kwargs):
|
def save (self, *args, **kwargs):
|
||||||
if self.episode:
|
if self.episode:
|
||||||
self.program = self.episode.parent
|
self.program = self.episode.parent
|
||||||
super(Diffusion, self).save(*args, **kwargs)
|
super(Diffusion, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__ (self):
|
||||||
|
return self.program.title + ' on ' + str(self.start) \
|
||||||
|
+ str(self.type)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Diffusion')
|
verbose_name = _('Diffusion')
|
||||||
|
|
|
@ -1,8 +1,33 @@
|
||||||
{% extends "admin/base.html" %}
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
{% block extrahead %}
|
{% block extrahead %}
|
||||||
|
{% include 'autocomplete_light/static.html' %}
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
/** autocomplete override **/
|
||||||
|
.autocomplete-light-widget .deck [data-value] .remove {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-light-widget .deck [data-value],
|
||||||
|
.autocomplete-light-widget .deck .choice {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group .add-related,
|
||||||
|
.inline-group .add-related {
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** suit **/
|
||||||
|
.controls textarea,
|
||||||
|
.controls .vTextField {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** grappelli **/
|
||||||
.grp-autocomplete-wrapper-m2m:focus, .grp-autocomplete-wrapper-m2m.grp-state-focus,
|
.grp-autocomplete-wrapper-m2m:focus, .grp-autocomplete-wrapper-m2m.grp-state-focus,
|
||||||
.grp-autocomplete-wrapper-m2m {
|
.grp-autocomplete-wrapper-m2m {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
@ -19,6 +44,7 @@
|
||||||
float: none;
|
float: none;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user