@ -72,10 +72,10 @@ class SoundFile:
 | 
				
			|||||||
        sound.save()
 | 
					        sound.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not sound.episodesound_set.all().exists():
 | 
					        if not sound.episodesound_set.all().exists():
 | 
				
			||||||
            self.find_episode_sound(sound)
 | 
					            self.create_episode_sound(sound)
 | 
				
			||||||
        return sound
 | 
					        return sound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def find_episode_sound(self, sound):
 | 
					    def create_episode_sound(self, sound):
 | 
				
			||||||
        episode = sound.find_episode()
 | 
					        episode = sound.find_episode()
 | 
				
			||||||
        if episode:
 | 
					        if episode:
 | 
				
			||||||
            # FIXME: position from name
 | 
					            # FIXME: position from name
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.conf import settings as d_settings
 | 
				
			||||||
from django.db import models
 | 
					from django.db import models
 | 
				
			||||||
from django.utils.functional import cached_property
 | 
					from django.utils.functional import cached_property
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
@ -105,12 +106,11 @@ class EpisodeSoundQuerySet(models.QuerySet):
 | 
				
			|||||||
        return self.available().filter(broadcast=True)
 | 
					        return self.available().filter(broadcast=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def playlist(self, order="position"):
 | 
					    def playlist(self, order="position"):
 | 
				
			||||||
 | 
					        # TODO: subquery expression
 | 
				
			||||||
        if order:
 | 
					        if order:
 | 
				
			||||||
            self = self.order_by(order)
 | 
					            self = self.order_by(order)
 | 
				
			||||||
        return [
 | 
					        query = self.filter(sound__file__isnull=False, sound__is_removed=False).values_list("sound__file", flat=True)
 | 
				
			||||||
            os.path.join(settings.MEDIA_ROOT, file)
 | 
					        return [os.path.join(d_settings.MEDIA_ROOT, file) for file in query]
 | 
				
			||||||
            for file in self.filter(file__isnull=False, is_removed=False).Values_list("file", flat=True)
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EpisodeSound(models.Model):
 | 
					class EpisodeSound(models.Model):
 | 
				
			||||||
 | 
				
			|||||||
@ -91,12 +91,12 @@ def schedule_post_save(sender, instance, created, *args, **kwargs):
 | 
				
			|||||||
def schedule_pre_delete(sender, instance, *args, **kwargs):
 | 
					def schedule_pre_delete(sender, instance, *args, **kwargs):
 | 
				
			||||||
    """Delete later corresponding diffusion to a changed schedule."""
 | 
					    """Delete later corresponding diffusion to a changed schedule."""
 | 
				
			||||||
    Diffusion.objects.filter(schedule=instance).after(tz.now()).delete()
 | 
					    Diffusion.objects.filter(schedule=instance).after(tz.now()).delete()
 | 
				
			||||||
    Episode.objects.filter(diffusion__isnull=True, content__isnull=True, sound__isnull=True).delete()
 | 
					    Episode.objects.filter(diffusion__isnull=True, content__isnull=True, episodesound__isnull=True).delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@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, episodesound__isnull=True).delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@receiver(signals.post_delete, sender=Sound)
 | 
					@receiver(signals.post_delete, sender=Sound)
 | 
				
			||||||
 | 
				
			|||||||
@ -26,16 +26,11 @@ class SoundQuerySet(FileQuerySet):
 | 
				
			|||||||
        """Return sounds that are archives."""
 | 
					        """Return sounds that are archives."""
 | 
				
			||||||
        return self.filter(broadcast=True)
 | 
					        return self.filter(broadcast=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def playlist(self, broadcast=True, order_by=True):
 | 
					    def playlist(self, order_by="file"):
 | 
				
			||||||
        """Return files absolute paths as a flat list (exclude sound without
 | 
					        """Return files absolute paths as a flat list (exclude sound without
 | 
				
			||||||
        path).
 | 
					        path)."""
 | 
				
			||||||
 | 
					 | 
				
			||||||
        If `order_by` is True, order by path.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if broadcast:
 | 
					 | 
				
			||||||
            self = self.broadcast()
 | 
					 | 
				
			||||||
        if order_by:
 | 
					        if order_by:
 | 
				
			||||||
            self = self.order_by("file")
 | 
					            self = self.order_by(order_by)
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            os.path.join(conf.MEDIA_ROOT, file)
 | 
					            os.path.join(conf.MEDIA_ROOT, file)
 | 
				
			||||||
            for file in self.filter(file__isnull=False).values_list("file", flat=True)
 | 
					            for file in self.filter(file__isnull=False).values_list("file", flat=True)
 | 
				
			||||||
@ -66,6 +61,8 @@ class Sound(File):
 | 
				
			|||||||
        help_text=_("The sound is broadcasted on air"),
 | 
					        help_text=_("The sound is broadcasted on air"),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    objects = SoundQuerySet.as_manager()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        verbose_name = _("Sound file")
 | 
					        verbose_name = _("Sound file")
 | 
				
			||||||
        verbose_name_plural = _("Sound files")
 | 
					        verbose_name_plural = _("Sound files")
 | 
				
			||||||
 | 
				
			|||||||
@ -131,25 +131,32 @@ def episode(episodes):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def podcasts(episodes):
 | 
					def sound(program):
 | 
				
			||||||
    items = []
 | 
					    return baker.make(models.Sound, file="tmp/test.wav", program=program)
 | 
				
			||||||
    for episode in episodes:
 | 
					 | 
				
			||||||
        sounds = baker.prepare(
 | 
					 | 
				
			||||||
            models.Sound,
 | 
					 | 
				
			||||||
            episode=episode,
 | 
					 | 
				
			||||||
            program=episode.program,
 | 
					 | 
				
			||||||
            is_public=True,
 | 
					 | 
				
			||||||
            _quantity=2,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        for i, sound in enumerate(sounds):
 | 
					 | 
				
			||||||
            sound.file = f"test_sound_{episode.pk}_{i}.mp3"
 | 
					 | 
				
			||||||
        items += sounds
 | 
					 | 
				
			||||||
    return items
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def sound(program):
 | 
					def sounds(program):
 | 
				
			||||||
    return baker.make(models.Sound, file="tmp/test.wav", program=program)
 | 
					    objs = [
 | 
				
			||||||
 | 
					        models.Sound(program=program, file=f"tmp/test-{i}.wav", broadcast=(i == 0), is_downloadable=(i == 1))
 | 
				
			||||||
 | 
					        for i in range(0, 3)
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    models.Sound.objects.bulk_create(objs)
 | 
				
			||||||
 | 
					    return objs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def podcasts(episode, sounds):
 | 
				
			||||||
 | 
					    objs = [
 | 
				
			||||||
 | 
					        models.EpisodeSound(
 | 
				
			||||||
 | 
					            episode=episode,
 | 
				
			||||||
 | 
					            sound=sound,
 | 
				
			||||||
 | 
					            broadcast=True,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        for sound in sounds
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    models.EpisodeSound.objects.bulk_create(objs)
 | 
				
			||||||
 | 
					    return objs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +1,12 @@
 | 
				
			|||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datetime import timedelta
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings as conf
 | 
					from django.conf import settings as conf
 | 
				
			||||||
from django.utils import timezone as tz
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aircox import models
 | 
					 | 
				
			||||||
from aircox.controllers.sound_file import SoundFile
 | 
					from aircox.controllers.sound_file import SoundFile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# FIXME: use from tests.models.sound
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def path_infos():
 | 
					def path_infos():
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
@ -27,6 +25,7 @@ def path_infos():
 | 
				
			|||||||
            "day": 2,
 | 
					            "day": 2,
 | 
				
			||||||
            "hour": 10,
 | 
					            "hour": 10,
 | 
				
			||||||
            "minute": 13,
 | 
					            "minute": 13,
 | 
				
			||||||
 | 
					            "n": None,
 | 
				
			||||||
            "name": "Sample 2",
 | 
					            "name": "Sample 2",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "test/20220103_1_sample_3.mp3": {
 | 
					        "test/20220103_1_sample_3.mp3": {
 | 
				
			||||||
@ -56,42 +55,25 @@ def sound_files(path_infos):
 | 
				
			|||||||
    return {k: r for k, r in ((path, SoundFile(conf.MEDIA_ROOT + "/" + path)) for path in path_infos.keys())}
 | 
					    return {k: r for k, r in ((path, SoundFile(conf.MEDIA_ROOT + "/" + path)) for path in path_infos.keys())}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def sound_file(sound_files):
 | 
				
			||||||
 | 
					    return next(sound_files.items())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sound_path(sound_files):
 | 
					def test_sound_path(sound_files):
 | 
				
			||||||
    for path, sound_file in sound_files.items():
 | 
					    for path, sound_file in sound_files.items():
 | 
				
			||||||
        assert path == sound_file.sound_path
 | 
					        assert path == sound_file.sound_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_read_path(path_infos, sound_files):
 | 
					class TestSoundFile:
 | 
				
			||||||
    for path, sound_file in sound_files.items():
 | 
					    def sound_path(self, sound_file):
 | 
				
			||||||
        expected = path_infos[path]
 | 
					        assert sound_file[0] == sound_file[1].sound_path
 | 
				
			||||||
        result = sound_file.read_path(path)
 | 
					 | 
				
			||||||
        # remove None values
 | 
					 | 
				
			||||||
        result = {k: v for k, v in result.items() if v is not None}
 | 
					 | 
				
			||||||
        assert expected == result, "path: {}".format(path)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sync(self):
 | 
				
			||||||
 | 
					        raise NotImplementedError("test is not implemented")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _setup_diff(program, info):
 | 
					    def create_episode_sound(self):
 | 
				
			||||||
    episode = models.Episode(program=program, title="test-episode")
 | 
					        raise NotImplementedError("test is not implemented")
 | 
				
			||||||
    at = tz.datetime(**{k: info[k] for k in ("year", "month", "day", "hour", "minute") if info.get(k)})
 | 
					 | 
				
			||||||
    at = tz.make_aware(at)
 | 
					 | 
				
			||||||
    diff = models.Diffusion(episode=episode, start=at, end=at + timedelta(hours=1))
 | 
					 | 
				
			||||||
    episode.save()
 | 
					 | 
				
			||||||
    diff.save()
 | 
					 | 
				
			||||||
    return diff
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _on_delete(self):
 | 
				
			||||||
@pytest.mark.django_db(transaction=True)
 | 
					        raise NotImplementedError("test is not implemented")
 | 
				
			||||||
def test_find_episode(sound_files):
 | 
					 | 
				
			||||||
    station = models.Station(name="test-station")
 | 
					 | 
				
			||||||
    program = models.Program(station=station, title="test")
 | 
					 | 
				
			||||||
    station.save()
 | 
					 | 
				
			||||||
    program.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for path, sound_file in sound_files.items():
 | 
					 | 
				
			||||||
        infos = sound_file.read_path(path)
 | 
					 | 
				
			||||||
        diff = _setup_diff(program, infos)
 | 
					 | 
				
			||||||
        sound = models.Sound(program=diff.program, file=path)
 | 
					 | 
				
			||||||
        result = sound_file.find_episode(sound, infos)
 | 
					 | 
				
			||||||
        assert diff.episode == result
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # TODO: find_playlist, sync
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -223,22 +223,19 @@ class TestSoundMonitor:
 | 
				
			|||||||
            [
 | 
					            [
 | 
				
			||||||
                (("scan all programs...",), {}),
 | 
					                (("scan all programs...",), {}),
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
            + [
 | 
					            + [((f"#{program.id} {program.title}",), {}) for program in programs]
 | 
				
			||||||
                ((f"#{program.id} {program.title}",), {})
 | 
					 | 
				
			||||||
                for program in programs
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        assert dirs == [program.abspath for program in programs]
 | 
					        assert dirs == [program.abspath for program in programs]
 | 
				
			||||||
        traces = tuple(
 | 
					        traces = tuple(
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    (
 | 
					                    (
 | 
				
			||||||
                        (program, settings.SOUND_ARCHIVES_SUBDIR),
 | 
					                        (program, settings.SOUND_BROADCASTS_SUBDIR),
 | 
				
			||||||
                        {"logger": logger, "type": Sound.TYPE_ARCHIVE},
 | 
					                        {"logger": logger, "broadcast": True},
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    (
 | 
					                    (
 | 
				
			||||||
                        (program, settings.SOUND_EXCERPTS_SUBDIR),
 | 
					                        (program, settings.SOUND_EXCERPTS_SUBDIR),
 | 
				
			||||||
                        {"logger": logger, "type": Sound.TYPE_EXCERPT},
 | 
					                        {"logger": logger, "broadcast": False},
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
                for program in programs
 | 
					                for program in programs
 | 
				
			||||||
@ -247,6 +244,7 @@ class TestSoundMonitor:
 | 
				
			|||||||
        traces_flat = tuple([item for sublist in traces for item in sublist])
 | 
					        traces_flat = tuple([item for sublist in traces for item in sublist])
 | 
				
			||||||
        assert interface._traces("scan_for_program") == traces_flat
 | 
					        assert interface._traces("scan_for_program") == traces_flat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO / FIXME
 | 
				
			||||||
    def broken_test_monitor(self, monitor, monitor_interfaces, logger):
 | 
					    def broken_test_monitor(self, monitor, monitor_interfaces, logger):
 | 
				
			||||||
        def sleep(*args, **kwargs):
 | 
					        def sleep(*args, **kwargs):
 | 
				
			||||||
            monitor.stop()
 | 
					            monitor.stop()
 | 
				
			||||||
@ -260,6 +258,7 @@ class TestSoundMonitor:
 | 
				
			|||||||
        assert observer
 | 
					        assert observer
 | 
				
			||||||
        schedules = observer._traces("schedule")
 | 
					        schedules = observer._traces("schedule")
 | 
				
			||||||
        for (handler, *_), kwargs in schedules:
 | 
					        for (handler, *_), kwargs in schedules:
 | 
				
			||||||
 | 
					            breakpoint()
 | 
				
			||||||
            assert isinstance(handler, sound_monitor.MonitorHandler)
 | 
					            assert isinstance(handler, sound_monitor.MonitorHandler)
 | 
				
			||||||
            assert isinstance(handler.pool, futures.ThreadPoolExecutor)
 | 
					            assert isinstance(handler.pool, futures.ThreadPoolExecutor)
 | 
				
			||||||
            assert (handler.subdir, handler.type) in (
 | 
					            assert (handler.subdir, handler.type) in (
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										122
									
								
								aircox/tests/models/test_sound.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								aircox/tests/models/test_sound.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					from datetime import timedelta
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					from django.utils import timezone as tz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox import models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def path_infos():
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        "test/20220101_10h13_1_sample_1.mp3": {
 | 
				
			||||||
 | 
					            "year": 2022,
 | 
				
			||||||
 | 
					            "month": 1,
 | 
				
			||||||
 | 
					            "day": 1,
 | 
				
			||||||
 | 
					            "hour": 10,
 | 
				
			||||||
 | 
					            "minute": 13,
 | 
				
			||||||
 | 
					            "n": 1,
 | 
				
			||||||
 | 
					            "name": "Sample 1",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "test/20220102_10h13_sample_2.mp3": {
 | 
				
			||||||
 | 
					            "year": 2022,
 | 
				
			||||||
 | 
					            "month": 1,
 | 
				
			||||||
 | 
					            "day": 2,
 | 
				
			||||||
 | 
					            "hour": 10,
 | 
				
			||||||
 | 
					            "minute": 13,
 | 
				
			||||||
 | 
					            "n": None,
 | 
				
			||||||
 | 
					            "name": "Sample 2",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "test/20220103_1_sample_3.mp3": {
 | 
				
			||||||
 | 
					            "year": 2022,
 | 
				
			||||||
 | 
					            "month": 1,
 | 
				
			||||||
 | 
					            "day": 3,
 | 
				
			||||||
 | 
					            "hour": None,
 | 
				
			||||||
 | 
					            "minute": None,
 | 
				
			||||||
 | 
					            "n": 1,
 | 
				
			||||||
 | 
					            "name": "Sample 3",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "test/20220104_sample_4.mp3": {
 | 
				
			||||||
 | 
					            "year": 2022,
 | 
				
			||||||
 | 
					            "month": 1,
 | 
				
			||||||
 | 
					            "day": 4,
 | 
				
			||||||
 | 
					            "hour": None,
 | 
				
			||||||
 | 
					            "minute": None,
 | 
				
			||||||
 | 
					            "n": None,
 | 
				
			||||||
 | 
					            "name": "Sample 4",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "test/20220105.mp3": {
 | 
				
			||||||
 | 
					            "year": 2022,
 | 
				
			||||||
 | 
					            "month": 1,
 | 
				
			||||||
 | 
					            "day": 5,
 | 
				
			||||||
 | 
					            "hour": None,
 | 
				
			||||||
 | 
					            "minute": None,
 | 
				
			||||||
 | 
					            "n": None,
 | 
				
			||||||
 | 
					            "name": "20220105",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestSoundQuerySet:
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_downloadable(self, sounds):
 | 
				
			||||||
 | 
					        query = models.Sound.objects.downloadable().values_list("is_downloadable", flat=True)
 | 
				
			||||||
 | 
					        assert set(query) == {True}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_broadcast(self, sounds):
 | 
				
			||||||
 | 
					        query = models.Sound.objects.broadcast().values_list("broadcast", flat=True)
 | 
				
			||||||
 | 
					        assert set(query) == {True}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_playlist(self, sounds):
 | 
				
			||||||
 | 
					        expected = [os.path.join(settings.MEDIA_ROOT, s.file.path) for s in sounds]
 | 
				
			||||||
 | 
					        assert models.Sound.objects.all().playlist() == expected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestSound:
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_read_path(self, path_infos):
 | 
				
			||||||
 | 
					        for path, expected in path_infos.items():
 | 
				
			||||||
 | 
					            result = models.Sound.read_path(path)
 | 
				
			||||||
 | 
					            assert expected == result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test__as_name(self):
 | 
				
			||||||
 | 
					        name = "some_1_file"
 | 
				
			||||||
 | 
					        assert models.Sound._as_name(name) == "Some 1 File"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_diff(self, program, info):
 | 
				
			||||||
 | 
					        episode = models.Episode(program=program, title="test-episode")
 | 
				
			||||||
 | 
					        at = tz.datetime(**{k: info[k] for k in ("year", "month", "day", "hour", "minute") if info.get(k)})
 | 
				
			||||||
 | 
					        at = tz.make_aware(at)
 | 
				
			||||||
 | 
					        diff = models.Diffusion(episode=episode, start=at, end=at + timedelta(hours=1))
 | 
				
			||||||
 | 
					        episode.save()
 | 
				
			||||||
 | 
					        diff.save()
 | 
				
			||||||
 | 
					        return diff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db(transaction=True)
 | 
				
			||||||
 | 
					    def test_find_episode(self, program, path_infos):
 | 
				
			||||||
 | 
					        for path, infos in path_infos.items():
 | 
				
			||||||
 | 
					            diff = self._setup_diff(program, infos)
 | 
				
			||||||
 | 
					            sound = models.Sound(program=diff.program, file=path)
 | 
				
			||||||
 | 
					            result = sound.find_episode(infos)
 | 
				
			||||||
 | 
					            assert diff.episode == result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_find_playlist(self):
 | 
				
			||||||
 | 
					        raise NotImplementedError("test is not implemented")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_get_upload_dir(self):
 | 
				
			||||||
 | 
					        raise NotImplementedError("test is not implemented")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_sync_fs(self):
 | 
				
			||||||
 | 
					        raise NotImplementedError("test is not implemented")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_read_metadata(self):
 | 
				
			||||||
 | 
					        raise NotImplementedError("test is not implemented")
 | 
				
			||||||
@ -1,10 +1,8 @@
 | 
				
			|||||||
 | 
					# FIXME: this should be cleaner
 | 
				
			||||||
from itertools import chain
 | 
					from itertools import chain
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.core.files.uploadedfile import SimpleUploadedFile
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from aircox.models import Program
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.django_db()
 | 
					@pytest.mark.django_db()
 | 
				
			||||||
@ -22,20 +20,6 @@ def test_edit_program(user, client, program):
 | 
				
			|||||||
    assert b"foobar" in response.content
 | 
					    assert b"foobar" in response.content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.django_db()
 | 
					 | 
				
			||||||
def test_add_cover(user, client, program, png_content):
 | 
					 | 
				
			||||||
    assert program.cover is None
 | 
					 | 
				
			||||||
    user.groups.add(program.editors)
 | 
					 | 
				
			||||||
    client.force_login(user)
 | 
					 | 
				
			||||||
    cover = SimpleUploadedFile("cover1.png", png_content, content_type="image/png")
 | 
					 | 
				
			||||||
    r = client.post(
 | 
					 | 
				
			||||||
        reverse("program-edit", kwargs={"pk": program.pk}), {"content": "foobar", "new_cover": cover}, follow=True
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    assert r.status_code == 200
 | 
					 | 
				
			||||||
    p = Program.objects.get(pk=program.pk)
 | 
					 | 
				
			||||||
    assert "cover1.png" in p.cover.url
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@pytest.mark.django_db()
 | 
					@pytest.mark.django_db()
 | 
				
			||||||
def test_edit_tracklist(user, client, program, episode, tracks):
 | 
					def test_edit_tracklist(user, client, program, episode, tracks):
 | 
				
			||||||
    user.groups.add(program.editors)
 | 
					    user.groups.add(program.editors)
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,9 @@ class FakeView:
 | 
				
			|||||||
    def ___init__(self):
 | 
					    def ___init__(self):
 | 
				
			||||||
        self.kwargs = {}
 | 
					        self.kwargs = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def dispatch(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, *args, **kwargs):
 | 
					    def get(self, *args, **kwargs):
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -43,7 +43,6 @@ class TestBaseView:
 | 
				
			|||||||
            "view": base_view,
 | 
					            "view": base_view,
 | 
				
			||||||
            "station": station,
 | 
					            "station": station,
 | 
				
			||||||
            "page": None,  # get_page() returns None
 | 
					            "page": None,  # get_page() returns None
 | 
				
			||||||
            "audio_streams": station.streams,
 | 
					 | 
				
			||||||
            "model": base_view.model,
 | 
					            "model": base_view.model,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -40,7 +40,7 @@ def parent_mixin():
 | 
				
			|||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def attach_mixin():
 | 
					def attach_mixin():
 | 
				
			||||||
    class Mixin(mixins.AttachedToMixin, FakeView):
 | 
					    class Mixin(mixins.AttachedToMixin, FakeView):
 | 
				
			||||||
        attach_to_value = models.StaticPage.ATTACH_TO_HOME
 | 
					        attach_to_value = models.StaticPage.Target.HOME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Mixin()
 | 
					    return Mixin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -105,10 +105,10 @@ class TestParentMixin:
 | 
				
			|||||||
    def test_get_parent_not_parent_url_kwargs(self, parent_mixin):
 | 
					    def test_get_parent_not_parent_url_kwargs(self, parent_mixin):
 | 
				
			||||||
        assert parent_mixin.get_parent(self.req) is None
 | 
					        assert parent_mixin.get_parent(self.req) is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_get_calls_parent(self, parent_mixin):
 | 
					    def test_dispatch_calls_parent(self, parent_mixin):
 | 
				
			||||||
        parent = "parent object"
 | 
					        parent = "parent object"
 | 
				
			||||||
        parent_mixin.get_parent = lambda *_, **kw: parent
 | 
					        parent_mixin.get_parent = lambda *_, **kw: parent
 | 
				
			||||||
        parent_mixin.get(self.req)
 | 
					        parent_mixin.dispatch(self.req)
 | 
				
			||||||
        assert parent_mixin.parent == parent
 | 
					        assert parent_mixin.parent == parent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.django_db
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
@ -120,7 +120,7 @@ class TestParentMixin:
 | 
				
			|||||||
        assert set(query) == episodes_id
 | 
					        assert set(query) == episodes_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_get_context_data_with_parent(self, parent_mixin):
 | 
					    def test_get_context_data_with_parent(self, parent_mixin):
 | 
				
			||||||
        parent_mixin.parent = Interface(cover="parent-cover")
 | 
					        parent_mixin.parent = Interface(cover=Interface(url="parent-cover"))
 | 
				
			||||||
        context = parent_mixin.get_context_data()
 | 
					        context = parent_mixin.get_context_data()
 | 
				
			||||||
        assert context["cover"] == "parent-cover"
 | 
					        assert context["cover"] == "parent-cover"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,6 @@ __all__ = ("BaseView", "BaseAPIView")
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseView(TemplateResponseMixin, ContextMixin):
 | 
					class BaseView(TemplateResponseMixin, ContextMixin):
 | 
				
			||||||
    header_template_name = "aircox/widgets/header.html"
 | 
					 | 
				
			||||||
    related_count = 4
 | 
					    related_count = 4
 | 
				
			||||||
    related_carousel_count = 8
 | 
					    related_carousel_count = 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,8 +49,8 @@ class BaseView(TemplateResponseMixin, ContextMixin):
 | 
				
			|||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
 | 
					        kwargs.setdefault("station", self.station)
 | 
				
			||||||
        kwargs.setdefault("page", self.get_page())
 | 
					        kwargs.setdefault("page", self.get_page())
 | 
				
			||||||
        kwargs.setdefault("header_template_name", self.header_template_name)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "model" not in kwargs:
 | 
					        if "model" not in kwargs:
 | 
				
			||||||
            model = getattr(self, "model", None) or hasattr(self, "object") and type(self.object)
 | 
					            model = getattr(self, "model", None) or hasattr(self, "object") and type(self.object)
 | 
				
			||||||
 | 
				
			|||||||
@ -133,8 +133,10 @@ class Monitor:
 | 
				
			|||||||
        # get sound
 | 
					        # get sound
 | 
				
			||||||
        diff = None
 | 
					        diff = None
 | 
				
			||||||
        sound = Sound.objects.path(air_uri).first()
 | 
					        sound = Sound.objects.path(air_uri).first()
 | 
				
			||||||
        if sound and sound.episode_id is not None:
 | 
					        if sound:
 | 
				
			||||||
            diff = Diffusion.objects.episode(id=sound.episode_id).on_air().now(air_time).first()
 | 
					            ids = sound.episodesound_set.values_list("episode_id", flat=True)
 | 
				
			||||||
 | 
					            if ids:
 | 
				
			||||||
 | 
					                diff = Diffusion.objects.filter(episode_id__in=ids).on_air().now(air_time).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # log sound on air
 | 
					        # log sound on air
 | 
				
			||||||
        return self.log(
 | 
					        return self.log(
 | 
				
			||||||
 | 
				
			|||||||
@ -146,24 +146,28 @@ def episode(program):
 | 
				
			|||||||
def sound(program, episode):
 | 
					def sound(program, episode):
 | 
				
			||||||
    sound = models.Sound(
 | 
					    sound = models.Sound(
 | 
				
			||||||
        program=program,
 | 
					        program=program,
 | 
				
			||||||
        episode=episode,
 | 
					 | 
				
			||||||
        name="sound",
 | 
					        name="sound",
 | 
				
			||||||
        type=models.Sound.TYPE_ARCHIVE,
 | 
					        broadcast=True,
 | 
				
			||||||
        position=0,
 | 
					 | 
				
			||||||
        file="sound.mp3",
 | 
					        file="sound.mp3",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    sound.save(check=False)
 | 
					    sound.save(sync=False)
 | 
				
			||||||
    return sound
 | 
					    return sound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def episode_sound(episode, sound):
 | 
				
			||||||
 | 
					    obj = models.EpisodeSound(episode=episode, sound=sound, position=0, broadcast=sound.broadcast)
 | 
				
			||||||
 | 
					    obj.save()
 | 
				
			||||||
 | 
					    return obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def sounds(program):
 | 
					def sounds(program):
 | 
				
			||||||
    items = [
 | 
					    items = [
 | 
				
			||||||
        models.Sound(
 | 
					        models.Sound(
 | 
				
			||||||
            name=f"sound {i}",
 | 
					            name=f"sound {i}",
 | 
				
			||||||
            program=program,
 | 
					            program=program,
 | 
				
			||||||
            type=models.Sound.TYPE_ARCHIVE,
 | 
					            broadcast=True,
 | 
				
			||||||
            position=i,
 | 
					 | 
				
			||||||
            file=f"sound-{i}.mp3",
 | 
					            file=f"sound-{i}.mp3",
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        for i in range(0, 3)
 | 
					        for i in range(0, 3)
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@ def monitor(streamer):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def diffusion(program, episode, sound):
 | 
					def diffusion(program, episode, episode_sound):
 | 
				
			||||||
    return baker.make(
 | 
					    return baker.make(
 | 
				
			||||||
        models.Diffusion,
 | 
					        models.Diffusion,
 | 
				
			||||||
        program=program,
 | 
					        program=program,
 | 
				
			||||||
@ -33,10 +33,10 @@ def diffusion(program, episode, sound):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def source(monitor, streamer, sound, diffusion):
 | 
					def source(monitor, streamer, episode_sound, diffusion):
 | 
				
			||||||
    source = next(monitor.streamer.playlists)
 | 
					    source = next(monitor.streamer.playlists)
 | 
				
			||||||
    source.uri = sound.file.path
 | 
					    source.uri = episode_sound.sound.file.path
 | 
				
			||||||
    source.episode_id = sound.episode_id
 | 
					    source.episode_id = episode_sound.episode_id
 | 
				
			||||||
    source.air_time = diffusion.start + tz.timedelta(seconds=10)
 | 
					    source.air_time = diffusion.start + tz.timedelta(seconds=10)
 | 
				
			||||||
    return source
 | 
					    return source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -185,7 +185,7 @@ class TestMonitor:
 | 
				
			|||||||
        monitor.trace_tracks(log)
 | 
					        monitor.trace_tracks(log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.django_db(transaction=True)
 | 
					    @pytest.mark.django_db(transaction=True)
 | 
				
			||||||
    def test_handle_diffusions(self, monitor, streamer, diffusion, sound):
 | 
					    def test_handle_diffusions(self, monitor, streamer, diffusion, episode_sound):
 | 
				
			||||||
        interface(
 | 
					        interface(
 | 
				
			||||||
            monitor,
 | 
					            monitor,
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
@ -67,7 +67,7 @@ class TestPlaylistSource:
 | 
				
			|||||||
    @pytest.mark.django_db
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
    def test_get_sound_queryset(self, playlist_source, sounds):
 | 
					    def test_get_sound_queryset(self, playlist_source, sounds):
 | 
				
			||||||
        query = playlist_source.get_sound_queryset()
 | 
					        query = playlist_source.get_sound_queryset()
 | 
				
			||||||
        assert all(r.program_id == playlist_source.program.pk and r.type == r.TYPE_ARCHIVE for r in query)
 | 
					        assert all(r.program_id == playlist_source.program.pk and r.broadcast for r in query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.django_db
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
    def test_get_playlist(self, playlist_source, sounds):
 | 
					    def test_get_playlist(self, playlist_source, sounds):
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user