make EpisodeUpdateView work
This commit is contained in:
		@ -32,6 +32,7 @@ class SoundInline(admin.TabularInline):
 | 
				
			|||||||
        "is_good_quality",
 | 
					        "is_good_quality",
 | 
				
			||||||
        "is_public",
 | 
					        "is_public",
 | 
				
			||||||
        "is_downloadable",
 | 
					        "is_downloadable",
 | 
				
			||||||
 | 
					        "is_removed",
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    readonly_fields = ["type", "audio", "duration", "is_good_quality"]
 | 
					    readonly_fields = ["type", "audio", "duration", "is_good_quality"]
 | 
				
			||||||
    extra = 0
 | 
					    extra = 0
 | 
				
			||||||
@ -89,11 +90,7 @@ class SoundAdmin(SortableAdminBase, admin.ModelAdmin):
 | 
				
			|||||||
    related.short_description = _("Program / Episode")
 | 
					    related.short_description = _("Program / Episode")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def audio(self, obj):
 | 
					    def audio(self, obj):
 | 
				
			||||||
        return (
 | 
					        return mark_safe('<audio src="{}" controls></audio>'.format(obj.file.url)) if not obj.is_removed else ""
 | 
				
			||||||
            mark_safe('<audio src="{}" controls></audio>'.format(obj.file.url))
 | 
					 | 
				
			||||||
            if obj.type != Sound.TYPE_REMOVED
 | 
					 | 
				
			||||||
            else ""
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    audio.short_description = _("Audio")
 | 
					    audio.short_description = _("Audio")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -99,7 +99,7 @@ class SoundFile:
 | 
				
			|||||||
            sound = Sound.objects.path(self.path).first()
 | 
					            sound = Sound.objects.path(self.path).first()
 | 
				
			||||||
            if sound:
 | 
					            if sound:
 | 
				
			||||||
                if keep_deleted:
 | 
					                if keep_deleted:
 | 
				
			||||||
                    sound.type = sound.TYPE_REMOVED
 | 
					                    sound.is_removed = True
 | 
				
			||||||
                    sound.check_on_file()
 | 
					                    sound.check_on_file()
 | 
				
			||||||
                    sound.save()
 | 
					                    sound.save()
 | 
				
			||||||
            return sound
 | 
					            return sound
 | 
				
			||||||
 | 
				
			|||||||
@ -80,6 +80,7 @@ TrackFormSet = modelformset_factory(
 | 
				
			|||||||
        "tags",
 | 
					        "tags",
 | 
				
			||||||
        "album",
 | 
					        "album",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					    can_delete=True,
 | 
				
			||||||
    extra=0,
 | 
					    extra=0,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
"""Track formset used in EpisodeUpdateView."""
 | 
					"""Track formset used in EpisodeUpdateView."""
 | 
				
			||||||
@ -94,6 +95,7 @@ SoundFormSet = modelformset_factory(
 | 
				
			|||||||
        "is_downloadable",
 | 
					        "is_downloadable",
 | 
				
			||||||
        "duration",
 | 
					        "duration",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					    can_delete=True,
 | 
				
			||||||
    extra=0,
 | 
					    extra=0,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
"""Sound formset used in EpisodeUpdateView."""
 | 
					"""Sound formset used in EpisodeUpdateView."""
 | 
				
			||||||
 | 
				
			|||||||
@ -6,8 +6,9 @@ from .log import Log, LogQuerySet
 | 
				
			|||||||
from .page import Category, Comment, NavItem, Page, PageQuerySet, StaticPage
 | 
					from .page import Category, Comment, NavItem, Page, PageQuerySet, StaticPage
 | 
				
			||||||
from .program import Program, ProgramChildQuerySet, ProgramQuerySet, Stream
 | 
					from .program import Program, ProgramChildQuerySet, ProgramQuerySet, Stream
 | 
				
			||||||
from .schedule import Schedule
 | 
					from .schedule import Schedule
 | 
				
			||||||
from .sound import Sound, SoundQuerySet, Track
 | 
					from .sound import Sound, SoundQuerySet
 | 
				
			||||||
from .station import Port, Station, StationQuerySet
 | 
					from .station import Port, Station, StationQuerySet
 | 
				
			||||||
 | 
					from .track import Track
 | 
				
			||||||
from .user_settings import UserSettings
 | 
					from .user_settings import UserSettings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = (
 | 
					__all__ = (
 | 
				
			||||||
 | 
				
			|||||||
@ -62,6 +62,7 @@ class Episode(Page):
 | 
				
			|||||||
                    podcast["name"] = f"{self.title} - {archive_index}"
 | 
					                    podcast["name"] = f"{self.title} - {archive_index}"
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    podcast["name"] = self.title
 | 
					                    podcast["name"] = self.title
 | 
				
			||||||
 | 
					                archive_index += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            podcasts[index]["cover"] = cover
 | 
					            podcasts[index]["cover"] = cover
 | 
				
			||||||
            podcasts[index]["page_url"] = self.get_absolute_url()
 | 
					            podcasts[index]["page_url"] = self.get_absolute_url()
 | 
				
			||||||
 | 
				
			|||||||
@ -8,8 +8,9 @@ from django.utils import timezone as tz
 | 
				
			|||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .diffusion import Diffusion
 | 
					from .diffusion import Diffusion
 | 
				
			||||||
from .sound import Sound, Track
 | 
					from .sound import Sound
 | 
				
			||||||
from .station import Station
 | 
					from .station import Station
 | 
				
			||||||
 | 
					from .track import Track
 | 
				
			||||||
from .page import Renderable
 | 
					from .page import Renderable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger("aircox")
 | 
					logger = logging.getLogger("aircox")
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.contrib.auth.models import Group, Permission, User
 | 
					from django.contrib.auth.models import Group, Permission, User
 | 
				
			||||||
from django.db import transaction
 | 
					from django.db import transaction
 | 
				
			||||||
from django.db.models import signals
 | 
					from django.db.models import signals
 | 
				
			||||||
@ -11,6 +13,7 @@ from .episode import Episode
 | 
				
			|||||||
from .page import Page
 | 
					from .page import Page
 | 
				
			||||||
from .program import Program
 | 
					from .program import Program
 | 
				
			||||||
from .schedule import Schedule
 | 
					from .schedule import Schedule
 | 
				
			||||||
 | 
					from .sound import Sound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Add a default group to a user when it is created. It also assigns a list
 | 
					# Add a default group to a user when it is created. It also assigns a list
 | 
				
			||||||
@ -94,3 +97,15 @@ def schedule_pre_delete(sender, instance, *args, **kwargs):
 | 
				
			|||||||
@receiver(signals.post_delete, sender=Diffusion)
 | 
					@receiver(signals.post_delete, sender=Diffusion)
 | 
				
			||||||
def diffusion_post_delete(sender, instance, *args, **kwargs):
 | 
					def diffusion_post_delete(sender, instance, *args, **kwargs):
 | 
				
			||||||
    Episode.objects.filter(diffusion__isnull=True, content__isnull=True, sound__isnull=True).delete()
 | 
					    Episode.objects.filter(diffusion__isnull=True, content__isnull=True, sound__isnull=True).delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@receiver(signals.post_delete, sender=Sound)
 | 
				
			||||||
 | 
					def delete_file(sender, instance, *args, **kwargs):
 | 
				
			||||||
 | 
					    """Deletes file on `post_delete`"""
 | 
				
			||||||
 | 
					    if not instance.file:
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    path = instance.file.path
 | 
				
			||||||
 | 
					    qs = sender.objects.filter(file=path)
 | 
				
			||||||
 | 
					    if not qs.exists() and os.path.exists(path):
 | 
				
			||||||
 | 
					        os.remove(path)
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,6 @@ from django.db import models
 | 
				
			|||||||
from django.db.models import Q
 | 
					from django.db.models import Q
 | 
				
			||||||
from django.utils import timezone as tz
 | 
					from django.utils import timezone as tz
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
from taggit.managers import TaggableManager
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aircox.conf import settings
 | 
					from aircox.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -16,7 +15,7 @@ from .program import Program
 | 
				
			|||||||
logger = logging.getLogger("aircox")
 | 
					logger = logging.getLogger("aircox")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = ("Sound", "SoundQuerySet", "Track")
 | 
					__all__ = ("Sound", "SoundQuerySet")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SoundQuerySet(models.QuerySet):
 | 
					class SoundQuerySet(models.QuerySet):
 | 
				
			||||||
@ -33,7 +32,7 @@ class SoundQuerySet(models.QuerySet):
 | 
				
			|||||||
        return self.filter(episode__diffusion__id=id)
 | 
					        return self.filter(episode__diffusion__id=id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def available(self):
 | 
					    def available(self):
 | 
				
			||||||
        return self.exclude(type=Sound.TYPE_REMOVED)
 | 
					        return self.exclude(is_removed=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def public(self):
 | 
					    def public(self):
 | 
				
			||||||
        """Return sounds available as podcasts."""
 | 
					        """Return sounds available as podcasts."""
 | 
				
			||||||
@ -85,12 +84,10 @@ class Sound(models.Model):
 | 
				
			|||||||
    TYPE_OTHER = 0x00
 | 
					    TYPE_OTHER = 0x00
 | 
				
			||||||
    TYPE_ARCHIVE = 0x01
 | 
					    TYPE_ARCHIVE = 0x01
 | 
				
			||||||
    TYPE_EXCERPT = 0x02
 | 
					    TYPE_EXCERPT = 0x02
 | 
				
			||||||
    TYPE_REMOVED = 0x03
 | 
					 | 
				
			||||||
    TYPE_CHOICES = (
 | 
					    TYPE_CHOICES = (
 | 
				
			||||||
        (TYPE_OTHER, _("other")),
 | 
					        (TYPE_OTHER, _("other")),
 | 
				
			||||||
        (TYPE_ARCHIVE, _("archive")),
 | 
					        (TYPE_ARCHIVE, _("archive")),
 | 
				
			||||||
        (TYPE_EXCERPT, _("excerpt")),
 | 
					        (TYPE_EXCERPT, _("excerpt")),
 | 
				
			||||||
        (TYPE_REMOVED, _("removed")),
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = models.CharField(_("name"), max_length=64)
 | 
					    name = models.CharField(_("name"), max_length=64)
 | 
				
			||||||
@ -116,6 +113,7 @@ class Sound(models.Model):
 | 
				
			|||||||
        default=0,
 | 
					        default=0,
 | 
				
			||||||
        help_text=_("position in the playlist"),
 | 
					        help_text=_("position in the playlist"),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    is_removed = models.BooleanField(_("removed"), default=False, help_text=_("file has been removed"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _upload_to(self, filename):
 | 
					    def _upload_to(self, filename):
 | 
				
			||||||
        subdir = settings.SOUND_ARCHIVES_SUBDIR if self.type == self.TYPE_ARCHIVE else settings.SOUND_EXCERPTS_SUBDIR
 | 
					        subdir = settings.SOUND_ARCHIVES_SUBDIR if self.type == self.TYPE_ARCHIVE else settings.SOUND_EXCERPTS_SUBDIR
 | 
				
			||||||
@ -201,16 +199,16 @@ class Sound(models.Model):
 | 
				
			|||||||
        Return True if there was changes.
 | 
					        Return True if there was changes.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if not self.file_exists():
 | 
					        if not self.file_exists():
 | 
				
			||||||
            if self.type == self.TYPE_REMOVED:
 | 
					            if self.is_removed:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            logger.debug("sound %s: has been removed", self.file.name)
 | 
					            logger.debug("sound %s: has been removed", self.file.name)
 | 
				
			||||||
            self.type = self.TYPE_REMOVED
 | 
					            self.is_removed = True
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # not anymore removed
 | 
					        # not anymore removed
 | 
				
			||||||
        changed = False
 | 
					        changed = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.type == self.TYPE_REMOVED and self.program:
 | 
					        if self.is_removed and self.program:
 | 
				
			||||||
            changed = True
 | 
					            changed = True
 | 
				
			||||||
            self.type = (
 | 
					            self.type = (
 | 
				
			||||||
                self.TYPE_ARCHIVE if self.file.name.startswith(self.program.archives_path) else self.TYPE_EXCERPT
 | 
					                self.TYPE_ARCHIVE if self.file.name.startswith(self.program.archives_path) else self.TYPE_EXCERPT
 | 
				
			||||||
@ -240,65 +238,3 @@ class Sound(models.Model):
 | 
				
			|||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
        super().__init__(*args, **kwargs)
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
        self.__check_name()
 | 
					        self.__check_name()
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Track(models.Model):
 | 
					 | 
				
			||||||
    """Track of a playlist of an object.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    The position can either be expressed as the position in the playlist
 | 
					 | 
				
			||||||
    or as the moment in seconds it started.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    episode = models.ForeignKey(
 | 
					 | 
				
			||||||
        Episode,
 | 
					 | 
				
			||||||
        models.CASCADE,
 | 
					 | 
				
			||||||
        blank=True,
 | 
					 | 
				
			||||||
        null=True,
 | 
					 | 
				
			||||||
        verbose_name=_("episode"),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    sound = models.ForeignKey(
 | 
					 | 
				
			||||||
        Sound,
 | 
					 | 
				
			||||||
        models.CASCADE,
 | 
					 | 
				
			||||||
        blank=True,
 | 
					 | 
				
			||||||
        null=True,
 | 
					 | 
				
			||||||
        verbose_name=_("sound"),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    position = models.PositiveSmallIntegerField(
 | 
					 | 
				
			||||||
        _("order"),
 | 
					 | 
				
			||||||
        default=0,
 | 
					 | 
				
			||||||
        help_text=_("position in the playlist"),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    timestamp = models.PositiveSmallIntegerField(
 | 
					 | 
				
			||||||
        _("timestamp"),
 | 
					 | 
				
			||||||
        blank=True,
 | 
					 | 
				
			||||||
        null=True,
 | 
					 | 
				
			||||||
        help_text=_("position (in seconds)"),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    title = models.CharField(_("title"), max_length=128)
 | 
					 | 
				
			||||||
    artist = models.CharField(_("artist"), max_length=128)
 | 
					 | 
				
			||||||
    album = models.CharField(_("album"), max_length=128, null=True, blank=True)
 | 
					 | 
				
			||||||
    tags = TaggableManager(verbose_name=_("tags"), blank=True)
 | 
					 | 
				
			||||||
    year = models.IntegerField(_("year"), blank=True, null=True)
 | 
					 | 
				
			||||||
    # FIXME: remove?
 | 
					 | 
				
			||||||
    info = models.CharField(
 | 
					 | 
				
			||||||
        _("information"),
 | 
					 | 
				
			||||||
        max_length=128,
 | 
					 | 
				
			||||||
        blank=True,
 | 
					 | 
				
			||||||
        null=True,
 | 
					 | 
				
			||||||
        help_text=_(
 | 
					 | 
				
			||||||
            "additional informations about this track, such as " "the version, if is it a remix, features, etc."
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        verbose_name = _("Track")
 | 
					 | 
				
			||||||
        verbose_name_plural = _("Tracks")
 | 
					 | 
				
			||||||
        ordering = ("position",)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __str__(self):
 | 
					 | 
				
			||||||
        return "{self.artist} -- {self.title} -- {self.position}".format(self=self)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def save(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        if (self.sound is None and self.episode is None) or (self.sound is not None and self.episode is not None):
 | 
					 | 
				
			||||||
            raise ValueError("sound XOR episode is required")
 | 
					 | 
				
			||||||
        super().save(*args, **kwargs)
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										72
									
								
								aircox/models/track.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								aircox/models/track.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					from taggit.managers import TaggableManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .episode import Episode
 | 
				
			||||||
 | 
					from .sound import Sound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ("Track",)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Track(models.Model):
 | 
				
			||||||
 | 
					    """Track of a playlist of an object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The position can either be expressed as the position in the playlist
 | 
				
			||||||
 | 
					    or as the moment in seconds it started.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    episode = models.ForeignKey(
 | 
				
			||||||
 | 
					        Episode,
 | 
				
			||||||
 | 
					        models.CASCADE,
 | 
				
			||||||
 | 
					        blank=True,
 | 
				
			||||||
 | 
					        null=True,
 | 
				
			||||||
 | 
					        verbose_name=_("episode"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    sound = models.ForeignKey(
 | 
				
			||||||
 | 
					        Sound,
 | 
				
			||||||
 | 
					        models.CASCADE,
 | 
				
			||||||
 | 
					        blank=True,
 | 
				
			||||||
 | 
					        null=True,
 | 
				
			||||||
 | 
					        verbose_name=_("sound"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    position = models.PositiveSmallIntegerField(
 | 
				
			||||||
 | 
					        _("order"),
 | 
				
			||||||
 | 
					        default=0,
 | 
				
			||||||
 | 
					        help_text=_("position in the playlist"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    timestamp = models.PositiveSmallIntegerField(
 | 
				
			||||||
 | 
					        _("timestamp"),
 | 
				
			||||||
 | 
					        blank=True,
 | 
				
			||||||
 | 
					        null=True,
 | 
				
			||||||
 | 
					        help_text=_("position (in seconds)"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    title = models.CharField(_("title"), max_length=128)
 | 
				
			||||||
 | 
					    artist = models.CharField(_("artist"), max_length=128)
 | 
				
			||||||
 | 
					    album = models.CharField(_("album"), max_length=128, null=True, blank=True)
 | 
				
			||||||
 | 
					    tags = TaggableManager(verbose_name=_("tags"), blank=True)
 | 
				
			||||||
 | 
					    year = models.IntegerField(_("year"), blank=True, null=True)
 | 
				
			||||||
 | 
					    # FIXME: remove?
 | 
				
			||||||
 | 
					    info = models.CharField(
 | 
				
			||||||
 | 
					        _("information"),
 | 
				
			||||||
 | 
					        max_length=128,
 | 
				
			||||||
 | 
					        blank=True,
 | 
				
			||||||
 | 
					        null=True,
 | 
				
			||||||
 | 
					        help_text=_(
 | 
				
			||||||
 | 
					            "additional informations about this track, such as " "the version, if is it a remix, features, etc."
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        verbose_name = _("Track")
 | 
				
			||||||
 | 
					        verbose_name_plural = _("Tracks")
 | 
				
			||||||
 | 
					        ordering = ("position",)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return "{self.artist} -- {self.title} -- {self.position}".format(self=self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        if (self.sound is None and self.episode is None) or (self.sound is not None and self.episode is not None):
 | 
				
			||||||
 | 
					            raise ValueError("sound XOR episode is required")
 | 
				
			||||||
 | 
					        super().save(*args, **kwargs)
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -8,8 +8,8 @@
 | 
				
			|||||||
    {{ admin_formset.non_form_errors }}
 | 
					    {{ admin_formset.non_form_errors }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <a-tracklist-editor
 | 
					    <a-tracklist-editor
 | 
				
			||||||
            :labels="{% track_inline_labels %}"
 | 
					            :labels="{% inline_labels %}"
 | 
				
			||||||
            :init-data="{% track_inline_data formset=formset %}"
 | 
					            :init-data="{% formset_inline_data formset=formset %}"
 | 
				
			||||||
            settings-url="{% url "api:user-settings" %}"
 | 
					            settings-url="{% url "api:user-settings" %}"
 | 
				
			||||||
            data-prefix="{{ formset.prefix }}-">
 | 
					            data-prefix="{{ formset.prefix }}-">
 | 
				
			||||||
        <template #title>
 | 
					        <template #title>
 | 
				
			||||||
 | 
				
			|||||||
@ -11,15 +11,15 @@ Context:
 | 
				
			|||||||
{% load aircox %}
 | 
					{% load aircox %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% if field.is_hidden or hidden %}
 | 
					{% if field.is_hidden or hidden %}
 | 
				
			||||||
<input type="hidden" name="{{ name }}" {% if vbind %}:value{% else %}value{% endif %}="{{ value|default:"" }}">
 | 
					<input type="hidden" name="{{ name }}" value="{{ value|default:"" }}">
 | 
				
			||||||
{% elif field|is_checkbox %}
 | 
					{% elif field|is_checkbox %}
 | 
				
			||||||
<input type="checkbox" class="checkbox" name="{{ name }}" {% if vbind %}:checked="{{ value }}"{% elif value %}checked{% endif %}>
 | 
					<input type="checkbox" class="checkbox" name="{{ name }}" {% if value %}checked{% endif %}>
 | 
				
			||||||
{% elif field|is_select %}
 | 
					{% elif field|is_select %}
 | 
				
			||||||
<select name="{{ name }}" class="select" {% if vbind %}:value{% else %}value{% endif %}="{{ value|default:"" }}">
 | 
					<select name="{{ name }}" class="select" value="{{ value|default:"" }}">
 | 
				
			||||||
    {% for value, label in field.widget.choices %}
 | 
					    {% for value, label in field.widget.choices %}
 | 
				
			||||||
    <option value="{{ value }}">{{ label }}</option>
 | 
					    <option value="{{ value }}">{{ label }}</option>
 | 
				
			||||||
    {% endfor %}
 | 
					    {% endfor %}
 | 
				
			||||||
</select>
 | 
					</select>
 | 
				
			||||||
{% else %}
 | 
					{% else %}
 | 
				
			||||||
<input type="text" class="input" name="{{ name }}" {% if vbind %}:value{% else %}value{% endif %}="{{ value|default:"" }}">
 | 
					<input type="text" class="input" name="{{ name }}" value="{{ value|default:"" }}">
 | 
				
			||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,12 @@ Context:
 | 
				
			|||||||
            <input type="hidden" name="{{ formset.prefix }}-TOTAL_FORMS"
 | 
					            <input type="hidden" name="{{ formset.prefix }}-TOTAL_FORMS"
 | 
				
			||||||
                :value="items.length || 0"/>
 | 
					                :value="items.length || 0"/>
 | 
				
			||||||
            <input type="hidden" name="{{ formset.prefix }}-INITIAL_FORMS"
 | 
					            <input type="hidden" name="{{ formset.prefix }}-INITIAL_FORMS"
 | 
				
			||||||
                value="{{ formset.initial_form_count }}"/>
 | 
					                {% if no_initial_form_count %}
 | 
				
			||||||
 | 
					                :value="items.length || 0"
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                value="{{ formset.initial_form_count }}"
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
            <input type="hidden" name="{{ formset.prefix }}-MIN_NUM_FORMS"
 | 
					            <input type="hidden" name="{{ formset.prefix }}-MIN_NUM_FORMS"
 | 
				
			||||||
                value="{{ formset.min_num }}"/>
 | 
					                value="{{ formset.min_num }}"/>
 | 
				
			||||||
            <input type="hidden" name="{{ formset.prefix }}-MAX_NUM_FORMS"
 | 
					            <input type="hidden" name="{{ formset.prefix }}-MAX_NUM_FORMS"
 | 
				
			||||||
@ -71,11 +76,13 @@ Context:
 | 
				
			|||||||
        {% if not field.widget.is_hidden and not field.is_readonly %}
 | 
					        {% if not field.widget.is_hidden and not field.is_readonly %}
 | 
				
			||||||
        <template v-slot:row-{{ name }}="{item,cell,value,attr,emit}">
 | 
					        <template v-slot:row-{{ name }}="{item,cell,value,attr,emit}">
 | 
				
			||||||
            <div class="field">
 | 
					            <div class="field">
 | 
				
			||||||
 | 
					                {% with full_name="'"|add:formset.prefix|add:"-' + cell.row + '-"|add:name|add:"'" %}
 | 
				
			||||||
                {% block row-field %}
 | 
					                {% block row-field %}
 | 
				
			||||||
                <div class="control">
 | 
					                <div class="control">
 | 
				
			||||||
                {% include "./form_field.html" with value="item.data."|add:name vbind=1 %}
 | 
					                {% include "./v_form_field.html" with value="item.data."|add:name name=full_name %}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                {% endblock %}
 | 
					                {% endblock %}
 | 
				
			||||||
 | 
					                {% endwith %}
 | 
				
			||||||
                <p v-for="error in item.error(attr)" class="help is-danger">
 | 
					                <p v-for="error in item.error(attr)" class="help is-danger">
 | 
				
			||||||
                    [[ error ]] !
 | 
					                    [[ error ]] !
 | 
				
			||||||
                </p>
 | 
					                </p>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,13 @@
 | 
				
			|||||||
{% extends "./list_editor.html" %}
 | 
					{% extends "./list_editor.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block outer %}
 | 
					{% block outer %}
 | 
				
			||||||
 | 
					    {% with no_initial_form_count=True %}
 | 
				
			||||||
    {% with tag_id="inline-sounds" %}
 | 
					    {% with tag_id="inline-sounds" %}
 | 
				
			||||||
    {% with tag="a-sound-list-editor" %}
 | 
					    {% with tag="a-sound-list-editor" %}
 | 
				
			||||||
        {{ block.super }}
 | 
					        {{ block.super }}
 | 
				
			||||||
    {% endwith %}
 | 
					    {% endwith %}
 | 
				
			||||||
    {% endwith %}
 | 
					    {% endwith %}
 | 
				
			||||||
 | 
					    {% endwith %}
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								aircox/templates/aircox/dashboard/v_form_field.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								aircox/templates/aircox/dashboard/v_form_field.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					{% comment %}
 | 
				
			||||||
 | 
					Render a form field instance as field (to be used when no model instance is provided). Value is binded as vue, class to Bulma
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Context:
 | 
				
			||||||
 | 
					- name: field name
 | 
				
			||||||
 | 
					- field: form field
 | 
				
			||||||
 | 
					- value: input ":v-model" attribute
 | 
				
			||||||
 | 
					- hidden: if True, hidden field
 | 
				
			||||||
 | 
					{% endcomment %}
 | 
				
			||||||
 | 
					{% load aircox %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% if field.is_hidden or hidden %}
 | 
				
			||||||
 | 
					<input type="hidden" :name="{{ name }}" :value="{{ value|default:"" }}">
 | 
				
			||||||
 | 
					{% elif field|is_checkbox %}
 | 
				
			||||||
 | 
					<input type="checkbox" class="checkbox" :name="{{ name }}" v-model="{{ value }}">
 | 
				
			||||||
 | 
					{% elif field|is_select %}
 | 
				
			||||||
 | 
					<select :name="{{ name }}" class="select" v-model="{{ value|default:"" }}">
 | 
				
			||||||
 | 
					    {% for value, label in field.widget.choices %}
 | 
				
			||||||
 | 
					    <option value="{{ value }}">{{ label }}</option>
 | 
				
			||||||
 | 
					    {% endfor %}
 | 
				
			||||||
 | 
					</select>
 | 
				
			||||||
 | 
					{% else %}
 | 
				
			||||||
 | 
					<input type="text" class="input" :name="{{ name }}" v-model="{{ value|default:"" }}">
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
@ -64,6 +64,7 @@ class EpisodeUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
 | 
				
			|||||||
    def get_tracklist_formset(self, episode, **kwargs):
 | 
					    def get_tracklist_formset(self, episode, **kwargs):
 | 
				
			||||||
        kwargs.update(
 | 
					        kwargs.update(
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                "prefix": "tracks",
 | 
				
			||||||
                "queryset": self.get_tracklist_queryset(episode),
 | 
					                "queryset": self.get_tracklist_queryset(episode),
 | 
				
			||||||
                "initial": {
 | 
					                "initial": {
 | 
				
			||||||
                    "episode": episode.id,
 | 
					                    "episode": episode.id,
 | 
				
			||||||
@ -78,6 +79,7 @@ class EpisodeUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
 | 
				
			|||||||
    def get_soundlist_formset(self, episode, **kwargs):
 | 
					    def get_soundlist_formset(self, episode, **kwargs):
 | 
				
			||||||
        kwargs.update(
 | 
					        kwargs.update(
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                "prefix": "sounds",
 | 
				
			||||||
                "queryset": self.get_soundlist_queryset(episode),
 | 
					                "queryset": self.get_soundlist_queryset(episode),
 | 
				
			||||||
                "initial": {
 | 
					                "initial": {
 | 
				
			||||||
                    "program": episode.parent_id,
 | 
					                    "program": episode.parent_id,
 | 
				
			||||||
@ -102,20 +104,29 @@ class EpisodeUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
 | 
				
			|||||||
        return forms.SoundCreateForm(**kwargs)
 | 
					        return forms.SoundCreateForm(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
        kwargs.update(
 | 
					        forms = (
 | 
				
			||||||
            {
 | 
					            ("soundlist_formset", self.get_soundlist_formset),
 | 
				
			||||||
                "soundlist_formset": self.get_soundlist_formset(self.object),
 | 
					            ("tracklist_formset", self.get_tracklist_formset),
 | 
				
			||||||
                "tracklist_formset": self.get_tracklist_formset(self.object),
 | 
					            ("sound_form", self.get_sound_form),
 | 
				
			||||||
                "sound_form": self.get_sound_form(self.object),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        for key, func in forms:
 | 
				
			||||||
 | 
					            if key not in kwargs:
 | 
				
			||||||
 | 
					                kwargs[key] = func(self.object)
 | 
				
			||||||
        return super().get_context_data(**kwargs)
 | 
					        return super().get_context_data(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request, *args, **kwargs):
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
        super().post(request, *args, **kwargs)
 | 
					        resp = super().post(request, *args, **kwargs)
 | 
				
			||||||
        formset = self.get_formset(request.POST)
 | 
					
 | 
				
			||||||
        if formset.is_valid():
 | 
					        formsets = {
 | 
				
			||||||
            formset.save()
 | 
					            "soundlist_formset": self.get_soundlist_formset(self.object, data=request.POST),
 | 
				
			||||||
            return super().form_valid(formset)
 | 
					            "tracklist_formset": self.get_tracklist_formset(self.object, data=request.POST),
 | 
				
			||||||
        else:
 | 
					        }
 | 
				
			||||||
            return super().form_valid(formset)  # form_invalid(formset)
 | 
					        invalid = False
 | 
				
			||||||
 | 
					        for formset in formsets.values():
 | 
				
			||||||
 | 
					            if not formset.is_valid():
 | 
				
			||||||
 | 
					                invalid = True
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                formset.save()
 | 
				
			||||||
 | 
					        if invalid:
 | 
				
			||||||
 | 
					            return self.get(request, **formsets)
 | 
				
			||||||
 | 
					        return resp
 | 
				
			||||||
 | 
				
			|||||||
@ -196,7 +196,6 @@ class PageUpdateView(BaseView, UpdateView):
 | 
				
			|||||||
    context_object_name = "page"
 | 
					    context_object_name = "page"
 | 
				
			||||||
    template_name = "aircox/page_form.html"
 | 
					    template_name = "aircox/page_form.html"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # FIXME: remove?
 | 
					 | 
				
			||||||
    def get_page(self):
 | 
					    def get_page(self):
 | 
				
			||||||
        return self.object
 | 
					        return self.object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -42,6 +42,12 @@ class SoundViewSet(BaseAPIView, viewsets.ModelViewSet):
 | 
				
			|||||||
    filter_backends = (drf_filters.DjangoFilterBackend,)
 | 
					    filter_backends = (drf_filters.DjangoFilterBackend,)
 | 
				
			||||||
    filterset_class = filters.SoundFilterSet
 | 
					    filterset_class = filters.SoundFilterSet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def perform_create(self, serializer):
 | 
				
			||||||
 | 
					        obj = serializer.save()
 | 
				
			||||||
 | 
					        # FIXME: hack to avoid "TYPE_REMOVED" status
 | 
				
			||||||
 | 
					        # -> file is saved to fs after object is saved to db
 | 
				
			||||||
 | 
					        obj.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TrackROViewSet(viewsets.ReadOnlyModelViewSet):
 | 
					class TrackROViewSet(viewsets.ReadOnlyModelViewSet):
 | 
				
			||||||
    """Track viewset used for auto completion."""
 | 
					    """Track viewset used for auto completion."""
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@
 | 
				
			|||||||
        <a-modal ref="modal" :title="labels && labels.add_sound">
 | 
					        <a-modal ref="modal" :title="labels && labels.add_sound">
 | 
				
			||||||
            <template #default>
 | 
					            <template #default>
 | 
				
			||||||
                <a-file-upload ref="file-upload" :url="soundUploadUrl" :label="labels.select_file" submitLabel="" @load="uploadDone"
 | 
					                <a-file-upload ref="file-upload" :url="soundUploadUrl" :label="labels.select_file" submitLabel="" @load="uploadDone"
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                    <template #preview="{upload}">
 | 
					                    <template #preview="{upload}">
 | 
				
			||||||
                        <slot name="upload-preview" :upload="upload"></slot>
 | 
					                        <slot name="upload-preview" :upload="upload"></slot>
 | 
				
			||||||
@ -24,6 +23,7 @@
 | 
				
			|||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
        </a-modal>
 | 
					        </a-modal>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <slot name="top" :set="set" :items="set.items"></slot>
 | 
				
			||||||
        <a-rows :set="set" :columns="columns"
 | 
					        <a-rows :set="set" :columns="columns"
 | 
				
			||||||
                :labels="initData.fields" :allow-create="true" :orderable="true"
 | 
					                :labels="initData.fields" :allow-create="true" :orderable="true"
 | 
				
			||||||
                @move="listItemMove">
 | 
					                @move="listItemMove">
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ window.aircox = {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onKeyPress(event) {
 | 
					    onKeyPress(/*event*/) {
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
        if(event.key == " ") {
 | 
					        if(event.key == " ") {
 | 
				
			||||||
            this.player.togglePlay()
 | 
					            this.player.togglePlay()
 | 
				
			||||||
            event.stopPropagation()
 | 
					            event.stopPropagation()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        */
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user