forked from rc/aircox
		
	bunch of work; separate publications from programs, start to work on website
This commit is contained in:
		@ -32,72 +32,49 @@ class DiffusionInline (admin.TabularInline):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TrackInline (SortableTabularInline):
 | 
			
		||||
    fields = ['artist', 'title', 'tags', 'position']
 | 
			
		||||
    fields = ['artist', 'name', 'tags', 'position']
 | 
			
		||||
    form = TrackForm
 | 
			
		||||
    model = Track
 | 
			
		||||
    sortable = 'position'
 | 
			
		||||
    extra = 10
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MetadataAdmin (admin.ModelAdmin):
 | 
			
		||||
    fieldsets = [
 | 
			
		||||
        ( None, {
 | 
			
		||||
            'fields': [ 'title', 'tags' ]
 | 
			
		||||
        }),
 | 
			
		||||
        ( None, {
 | 
			
		||||
            'fields': [ 'date', 'public' ],
 | 
			
		||||
        }),
 | 
			
		||||
    ]
 | 
			
		||||
class DescriptionAdmin (admin.ModelAdmin):
 | 
			
		||||
    fields = [ 'name', 'tags', 'description' ]
 | 
			
		||||
 | 
			
		||||
    def save_model (self, request, obj, form, change):
 | 
			
		||||
        # FIXME: if request.data.author?
 | 
			
		||||
        if not obj.author:
 | 
			
		||||
            obj.author = request.user
 | 
			
		||||
        obj.save()
 | 
			
		||||
    def tags (obj):
 | 
			
		||||
        return ', '.join(obj.tags.names())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PublicationAdmin (MetadataAdmin):
 | 
			
		||||
    fieldsets = copy.deepcopy(MetadataAdmin.fieldsets)
 | 
			
		||||
 | 
			
		||||
    list_display = ('id', 'title', 'date', 'public', 'parent')
 | 
			
		||||
    list_filter = ['date', 'public', 'parent', 'author']
 | 
			
		||||
    list_editable = ('public',)
 | 
			
		||||
    search_fields = ['title', 'content']
 | 
			
		||||
 | 
			
		||||
    fieldsets[0][1]['fields'].insert(1, 'subtitle')
 | 
			
		||||
    fieldsets[0][1]['fields'] += [ 'img', 'content' ]
 | 
			
		||||
    fieldsets[1][1]['fields'] += [ 'parent' ] #, 'meta' ],
 | 
			
		||||
    list_display = ['id', 'name', tags]
 | 
			
		||||
    list_filter = []
 | 
			
		||||
    search_fields = ['name',]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Sound)
 | 
			
		||||
class SoundAdmin (MetadataAdmin):
 | 
			
		||||
class SoundAdmin (DescriptionAdmin):
 | 
			
		||||
    fields = None
 | 
			
		||||
    fieldsets = [
 | 
			
		||||
        (None, { 'fields': ['title', 'tags', 'path' ] } ),
 | 
			
		||||
        (None, { 'fields': DescriptionAdmin.fields + ['path' ] } ),
 | 
			
		||||
        (None, { 'fields': ['duration', 'date', 'fragment' ] } )
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Stream)
 | 
			
		||||
class StreamAdmin (SortableModelAdmin):
 | 
			
		||||
    list_display = ('id', 'title', 'type', 'public', 'priority')
 | 
			
		||||
    list_editable = ('public',)
 | 
			
		||||
    list_display = ('id', 'name', 'type', 'priority')
 | 
			
		||||
    sortable = "priority"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Program)
 | 
			
		||||
class ProgramAdmin (PublicationAdmin):
 | 
			
		||||
    fieldsets = copy.deepcopy(PublicationAdmin.fieldsets)
 | 
			
		||||
class ProgramAdmin (DescriptionAdmin):
 | 
			
		||||
    fields = DescriptionAdmin.fields + ['stream']
 | 
			
		||||
    inlines = [ ScheduleInline ]
 | 
			
		||||
 | 
			
		||||
    fieldsets[1][1]['fields'] += ['email', 'url']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Episode)
 | 
			
		||||
class EpisodeAdmin (PublicationAdmin):
 | 
			
		||||
    fieldsets = copy.deepcopy(PublicationAdmin.fieldsets)
 | 
			
		||||
    list_filter = ['parent'] + PublicationAdmin.list_filter
 | 
			
		||||
 | 
			
		||||
    fieldsets[0][1]['fields'] += ['sounds']
 | 
			
		||||
class EpisodeAdmin (DescriptionAdmin):
 | 
			
		||||
    list_filter = ['program'] + DescriptionAdmin.list_filter
 | 
			
		||||
    fields = DescriptionAdmin.fields + ['sounds']
 | 
			
		||||
 | 
			
		||||
    inlines = (TrackInline, DiffusionInline)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,12 +33,12 @@ class TrackArtistAutocomplete(OneFieldAutocomplete):
 | 
			
		||||
al.register(TrackArtistAutocomplete)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TrackTitleAutocomplete(OneFieldAutocomplete):
 | 
			
		||||
    search_fields = ['title']
 | 
			
		||||
class TrackNameAutocomplete(OneFieldAutocomplete):
 | 
			
		||||
    search_fields = ['name']
 | 
			
		||||
    model = Track
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
al.register(TrackTitleAutocomplete)
 | 
			
		||||
al.register(TrackNameAutocomplete)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#class DiffusionAutocomplete(OneFieldAutocomplete):
 | 
			
		||||
 | 
			
		||||
@ -10,10 +10,10 @@ from programs.models import *
 | 
			
		||||
class TrackForm (forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Track
 | 
			
		||||
        fields = ['artist', 'title', 'tags', 'position']
 | 
			
		||||
        fields = ['artist', 'name', 'tags', 'position']
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'artist': al.TextWidget('TrackArtistAutocomplete'),
 | 
			
		||||
            'title': al.TextWidget('TrackTitleAutocomplete'),
 | 
			
		||||
            'name': al.TextWidget('TrackNameAutocomplete'),
 | 
			
		||||
            'tags': TaggitWidget('TagAutocomplete'),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ class Actions:
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def update (date):
 | 
			
		||||
        items = []
 | 
			
		||||
        for schedule in Schedule.objects.filter(parent__active = True):
 | 
			
		||||
        for schedule in Schedule.objects.filter(program__active = True):
 | 
			
		||||
            items += schedule.diffusions_of_month(date, exclude_saved = True)
 | 
			
		||||
            print('> {} new diffusions for schedule #{} ({})'.format(
 | 
			
		||||
                    len(items), schedule.id, str(schedule)
 | 
			
		||||
@ -47,7 +47,7 @@ class Actions:
 | 
			
		||||
                                      date__gt = date)
 | 
			
		||||
        items = []
 | 
			
		||||
        for diffusion in qs:
 | 
			
		||||
            schedules = Schedule.objects.filter(parent = diffusion.program)
 | 
			
		||||
            schedules = Schedule.objects.filter(program = diffusion.program)
 | 
			
		||||
            for schedule in schedules:
 | 
			
		||||
                if schedule.match(diffusion.date):
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.template.defaultfilters import slugify
 | 
			
		||||
from django.contrib.contenttypes.fields import GenericForeignKey
 | 
			
		||||
from django.contrib.contenttypes.models import ContentType
 | 
			
		||||
from django.utils.translation import ugettext as _, ugettext_lazy
 | 
			
		||||
from django.utils import timezone as tz
 | 
			
		||||
from django.utils.html import strip_tags
 | 
			
		||||
@ -27,27 +24,15 @@ def date_or_default (date, date_only = False):
 | 
			
		||||
    return date
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Metadata (models.Model):
 | 
			
		||||
    """
 | 
			
		||||
    meta is used to extend a model for future needs
 | 
			
		||||
    """
 | 
			
		||||
    author = models.ForeignKey (
 | 
			
		||||
        User,
 | 
			
		||||
        verbose_name = _('author'),
 | 
			
		||||
        blank = True, null = True,
 | 
			
		||||
    )
 | 
			
		||||
    title = models.CharField(
 | 
			
		||||
        _('title'),
 | 
			
		||||
class Description (models.Model):
 | 
			
		||||
    name = models.CharField (
 | 
			
		||||
        _('name'),
 | 
			
		||||
        max_length = 128,
 | 
			
		||||
    )
 | 
			
		||||
    date = models.DateTimeField(
 | 
			
		||||
        _('date'),
 | 
			
		||||
        default = tz.datetime.now,
 | 
			
		||||
    )
 | 
			
		||||
    public = models.BooleanField(
 | 
			
		||||
        _('public'),
 | 
			
		||||
        default = True,
 | 
			
		||||
        help_text = _('publication is public'),
 | 
			
		||||
    description = models.TextField (
 | 
			
		||||
        _('description'),
 | 
			
		||||
        max_length = 1024,
 | 
			
		||||
        blank = True, null = True
 | 
			
		||||
    )
 | 
			
		||||
    tags = TaggableManager(
 | 
			
		||||
        _('tags'),
 | 
			
		||||
@ -55,68 +40,18 @@ class Metadata (models.Model):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def get_slug_name (self):
 | 
			
		||||
        return slugify(self.title)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Publication (Metadata):
 | 
			
		||||
    subtitle = models.CharField(
 | 
			
		||||
        _('subtitle'),
 | 
			
		||||
        max_length = 128,
 | 
			
		||||
        blank = True,
 | 
			
		||||
    )
 | 
			
		||||
    img = models.ImageField(
 | 
			
		||||
        _('image'),
 | 
			
		||||
        upload_to = "images",
 | 
			
		||||
        blank = True,
 | 
			
		||||
    )
 | 
			
		||||
    content = models.TextField(
 | 
			
		||||
        _('content'),
 | 
			
		||||
        blank = True,
 | 
			
		||||
    )
 | 
			
		||||
    commentable = models.BooleanField(
 | 
			
		||||
        _('enable comments'),
 | 
			
		||||
        default = True,
 | 
			
		||||
        help_text = _('comments are enabled on this publication'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _exclude_args (allow_unpublished = False, prefix = ''):
 | 
			
		||||
        if allow_unpublished:
 | 
			
		||||
            return {}
 | 
			
		||||
 | 
			
		||||
        res = {}
 | 
			
		||||
        res[prefix + 'public'] = False
 | 
			
		||||
        res[prefix + 'date__gt'] = tz.now()
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_available (cl, first = False, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Return the result of filter(kargs) if the resulting publications
 | 
			
		||||
        is published and public
 | 
			
		||||
 | 
			
		||||
        Otherwise, return None
 | 
			
		||||
        """
 | 
			
		||||
        kwargs['public'] = True
 | 
			
		||||
        kwargs['date__lte'] = tz.now()
 | 
			
		||||
 | 
			
		||||
        e = cl.objects.filter(**kwargs)
 | 
			
		||||
 | 
			
		||||
        if first:
 | 
			
		||||
            return (e and e[0]) or None
 | 
			
		||||
        return e or None
 | 
			
		||||
        return slugify(self.name)
 | 
			
		||||
 | 
			
		||||
    def __str__ (self):
 | 
			
		||||
        return self.title + ' (' + str(self.id) + ')'
 | 
			
		||||
        if self.pk:
 | 
			
		||||
            return '#{} {}'.format(self.pk, self.name)
 | 
			
		||||
        return '{}'.format(self.name)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Track (models.Model):
 | 
			
		||||
class Track (Description):
 | 
			
		||||
    # 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
 | 
			
		||||
@ -127,13 +62,8 @@ class Track (models.Model):
 | 
			
		||||
        _('artist'),
 | 
			
		||||
        max_length = 128,
 | 
			
		||||
    )
 | 
			
		||||
    title = models.CharField(
 | 
			
		||||
        _('title'),
 | 
			
		||||
        max_length = 128,
 | 
			
		||||
    )
 | 
			
		||||
    tags = TaggableManager( blank = True )
 | 
			
		||||
    # position can be used to specify a position in seconds for non-stop
 | 
			
		||||
    # programs or a position in the playlist
 | 
			
		||||
    # position can be used to specify a position in seconds for non-
 | 
			
		||||
    # stop programs or a position in the playlist
 | 
			
		||||
    position = models.SmallIntegerField(
 | 
			
		||||
        default = 0,
 | 
			
		||||
        help_text=_('position in the playlist'),
 | 
			
		||||
@ -147,7 +77,7 @@ class Track (models.Model):
 | 
			
		||||
        verbose_name_plural = _('Tracks')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Sound (Metadata):
 | 
			
		||||
class Sound (Description):
 | 
			
		||||
    """
 | 
			
		||||
    A Sound is the representation of a sound, that can be:
 | 
			
		||||
    - An episode podcast/complete record
 | 
			
		||||
@ -158,7 +88,7 @@ class Sound (Metadata):
 | 
			
		||||
    public, then we can podcast it. If a Sound is a fragment, then it is not
 | 
			
		||||
    usable for diffusion.
 | 
			
		||||
 | 
			
		||||
    Each sound file can be associated to a filesystem's file or an embedded
 | 
			
		||||
    Each sound can be associated to a filesystem's file or an embedded
 | 
			
		||||
    code (for external podcasts).
 | 
			
		||||
    """
 | 
			
		||||
    path = models.FilePathField(
 | 
			
		||||
@ -177,10 +107,15 @@ class Sound (Metadata):
 | 
			
		||||
        _('duration'),
 | 
			
		||||
        blank = True, null = True,
 | 
			
		||||
    )
 | 
			
		||||
    public = models.BooleanField(
 | 
			
		||||
        _('public'),
 | 
			
		||||
        default = False,
 | 
			
		||||
        help_text = _("the element is public"),
 | 
			
		||||
    )
 | 
			
		||||
    fragment = models.BooleanField(
 | 
			
		||||
        _('incomplete sound'),
 | 
			
		||||
        default = False,
 | 
			
		||||
        help_text = _("the file has been cut"),
 | 
			
		||||
        help_text = _("the file is a cut"),
 | 
			
		||||
    )
 | 
			
		||||
    removed = models.BooleanField(
 | 
			
		||||
        default = False,
 | 
			
		||||
@ -198,6 +133,7 @@ class Sound (Metadata):
 | 
			
		||||
    def save (self, *args, **kwargs):
 | 
			
		||||
        if not self.pk:
 | 
			
		||||
            self.date = self.get_mtime()
 | 
			
		||||
 | 
			
		||||
        super(Sound, self).save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def __str__ (self):
 | 
			
		||||
@ -228,7 +164,7 @@ class Schedule (models.Model):
 | 
			
		||||
    for key, value in Frequency.items():
 | 
			
		||||
        ugettext_lazy(key)
 | 
			
		||||
 | 
			
		||||
    parent = models.ForeignKey(
 | 
			
		||||
    program = models.ForeignKey(
 | 
			
		||||
        'Program',
 | 
			
		||||
        blank = True, null = True,
 | 
			
		||||
    )
 | 
			
		||||
@ -243,7 +179,7 @@ class Schedule (models.Model):
 | 
			
		||||
    rerun = models.ForeignKey(
 | 
			
		||||
        'self',
 | 
			
		||||
        blank = True, null = True,
 | 
			
		||||
        help_text = "Schedule of a rerun",
 | 
			
		||||
        help_text = "Schedule of a rerun of this one",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def match (self, date = None, check_time = True):
 | 
			
		||||
@ -373,7 +309,7 @@ class Diffusion (models.Model):
 | 
			
		||||
        'default':      0x00,   # simple diffusion (done/planed)
 | 
			
		||||
        'unconfirmed':  0x01,   # scheduled by the generator but not confirmed for diffusion
 | 
			
		||||
        'cancel':       0x02,   # cancellation happened; used to inform users
 | 
			
		||||
        'restart':      0x03,   # manual restart; used to remix/give up antenna
 | 
			
		||||
        # 'restart':      0x03,   # manual restart; used to remix/give up antenna
 | 
			
		||||
        'stop':         0x04,   # diffusion has been forced to stop
 | 
			
		||||
    }
 | 
			
		||||
    for key, value in Type.items():
 | 
			
		||||
@ -424,12 +360,17 @@ class Stream (models.Model):
 | 
			
		||||
    for key, value in Type.items():
 | 
			
		||||
        ugettext_lazy(key)
 | 
			
		||||
 | 
			
		||||
    title = models.CharField(
 | 
			
		||||
        _('title'),
 | 
			
		||||
    name = models.CharField(
 | 
			
		||||
        _('name'),
 | 
			
		||||
        max_length = 32,
 | 
			
		||||
        blank = True,
 | 
			
		||||
        null = True,
 | 
			
		||||
    )
 | 
			
		||||
    public = models.BooleanField(
 | 
			
		||||
        _('public'),
 | 
			
		||||
        default = True,
 | 
			
		||||
        help_text = _('program list is public'),
 | 
			
		||||
    )
 | 
			
		||||
    type = models.SmallIntegerField(
 | 
			
		||||
        verbose_name = _('type'),
 | 
			
		||||
        choices = [ (y, x) for x,y in Type.items() ],
 | 
			
		||||
@ -439,11 +380,6 @@ class Stream (models.Model):
 | 
			
		||||
        default = 0,
 | 
			
		||||
        help_text = _('priority of the stream')
 | 
			
		||||
    )
 | 
			
		||||
    public = models.BooleanField(
 | 
			
		||||
        _('public'),
 | 
			
		||||
        default = True,
 | 
			
		||||
        help_text = _('program list is public'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # get info for:
 | 
			
		||||
    # - random lists
 | 
			
		||||
@ -453,23 +389,14 @@ class Stream (models.Model):
 | 
			
		||||
    #   - stream/pgm
 | 
			
		||||
 | 
			
		||||
    def __str__ (self):
 | 
			
		||||
        return '#{} {}'.format(self.priority, self.title)
 | 
			
		||||
        return '#{} {}'.format(self.priority, self.name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Program (Publication):
 | 
			
		||||
    parent = models.ForeignKey(
 | 
			
		||||
class Program (Description):
 | 
			
		||||
    stream = models.ForeignKey(
 | 
			
		||||
        Stream,
 | 
			
		||||
        verbose_name = _('stream'),
 | 
			
		||||
    )
 | 
			
		||||
    email = models.EmailField(
 | 
			
		||||
        _('email'),
 | 
			
		||||
        max_length = 128,
 | 
			
		||||
        null = True, blank = True,
 | 
			
		||||
    )
 | 
			
		||||
    url = models.URLField(
 | 
			
		||||
        _('website'),
 | 
			
		||||
        blank = True, null = True,
 | 
			
		||||
    )
 | 
			
		||||
    active = models.BooleanField(
 | 
			
		||||
        _('inactive'),
 | 
			
		||||
        default = True,
 | 
			
		||||
@ -491,8 +418,8 @@ class Program (Publication):
 | 
			
		||||
                return schedule
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Episode (Publication):
 | 
			
		||||
    parent = models.ForeignKey(
 | 
			
		||||
class Episode (Description):
 | 
			
		||||
    program = models.ForeignKey(
 | 
			
		||||
        Program,
 | 
			
		||||
        verbose_name = _('parent'),
 | 
			
		||||
        help_text = _('parent program'),
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user