- !88 pytest on existing tests - !89 reorganise settings (! see notes for deployment) Co-authored-by: bkfox <thomas bkfox net> Reviewed-on: #92
This commit is contained in:
parent
4bebc56a28
commit
0e183099ed
|
@ -12,17 +12,11 @@ repos:
|
|||
args:
|
||||
- --line-length=79
|
||||
- --exclude="""\.git|\.__pycache__|venv|_build|buck-out|build|dist"""
|
||||
- repo: https://github.com/PyCQA/autoflake.git
|
||||
rev: v2.0.2
|
||||
hooks:
|
||||
- id: autoflake
|
||||
args:
|
||||
- --remove-all-unused-imports
|
||||
- repo: https://github.com/PyCQA/flake8.git
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
exclude: instance/sample_settings.py
|
||||
exclude: instance/settings/
|
||||
- repo: https://github.com/PyCQA/docformatter.git
|
||||
rev: v1.5.1
|
||||
hooks:
|
||||
|
|
180
aircox/conf.py
Executable file
180
aircox/conf.py
Executable file
|
@ -0,0 +1,180 @@
|
|||
import os
|
||||
|
||||
import inspect
|
||||
|
||||
from django.conf import settings as d_settings
|
||||
|
||||
|
||||
__all__ = ("Settings", "settings")
|
||||
|
||||
|
||||
# code from django-fox
|
||||
class BaseSettings:
|
||||
"""Utility class used to load and save settings, can be used as model.
|
||||
|
||||
Some members are excluded from being configuration:
|
||||
- Protected/private members;
|
||||
- On django model, "objects" and "Meta";
|
||||
- Class declaration and callables
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
class MySettings(Settings):
|
||||
a = 13
|
||||
b = 12
|
||||
|
||||
my_settings = MySettings().load('MY_SETTINGS_KEY')
|
||||
print(my_settings.a, my_settings.get('b'))
|
||||
```
|
||||
|
||||
This will load values from django project settings.
|
||||
"""
|
||||
|
||||
def __init__(self, key, module=None):
|
||||
self.load(key, module)
|
||||
|
||||
def load(self, key, module=None):
|
||||
"""Load settings from module's item specified by its member name. When
|
||||
no module is provided, uses ``django.conf.settings``.
|
||||
|
||||
:param str key: module member name.
|
||||
:param module: configuration object.
|
||||
:returns self
|
||||
"""
|
||||
if module is None:
|
||||
module = d_settings
|
||||
settings = getattr(module, key, None)
|
||||
if settings:
|
||||
self.update(settings)
|
||||
return self
|
||||
|
||||
def update(self, settings):
|
||||
"""Update self's values from provided settings. ``settings`` can be an
|
||||
iterable of ``(key, value)``.
|
||||
|
||||
:param dict|Settings|iterable settings: value to update from.
|
||||
"""
|
||||
if isinstance(settings, (dict, Settings)):
|
||||
settings = settings.items()
|
||||
for key, value in settings:
|
||||
if self.is_config_item(key, value):
|
||||
setattr(self, key, value)
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""Return settings' value for provided key."""
|
||||
return getattr(self, key, default)
|
||||
|
||||
def items(self):
|
||||
"""Iterate over items members, as tupple of ``key, value``."""
|
||||
for key in dir(self):
|
||||
value = getattr(self, key)
|
||||
if self.is_config_item(key, value):
|
||||
yield key, value
|
||||
|
||||
def is_config_item(self, key, value):
|
||||
"""Return True if key/value item is a configuration setting."""
|
||||
if key.startswith("_") or callable(value) or inspect.isclass(value):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
# --- Global & misc
|
||||
DEFAULT_USER_GROUPS = {
|
||||
"radio hosts": (
|
||||
# TODO include content_type in order to avoid clash with potential
|
||||
# extra applications
|
||||
# aircox
|
||||
"change_program",
|
||||
"change_episode",
|
||||
"change_diffusion",
|
||||
"add_comment",
|
||||
"change_comment",
|
||||
"delete_comment",
|
||||
"add_article",
|
||||
"change_article",
|
||||
"delete_article",
|
||||
"change_sound",
|
||||
"add_track",
|
||||
"change_track",
|
||||
"delete_track",
|
||||
# taggit
|
||||
"add_tag",
|
||||
"change_tag",
|
||||
"delete_tag",
|
||||
# filer
|
||||
"add_folder",
|
||||
"change_folder",
|
||||
"delete_folder",
|
||||
"can_use_directory_listing",
|
||||
"add_image",
|
||||
"change_image",
|
||||
"delete_image",
|
||||
),
|
||||
}
|
||||
"""Groups to assign to users at their creation, along with the permissions
|
||||
to add to each group."""
|
||||
PROGRAMS_DIR = "programs"
|
||||
"""Directory for the programs data."""
|
||||
|
||||
@property
|
||||
def PROGRAMS_DIR_ABS(self):
|
||||
return os.path.join(d_settings.MEDIA_ROOT, self.PROGRAMS_DIR)
|
||||
|
||||
# --- Programs & episodes
|
||||
EPISODE_TITLE = "{program.title} - {date}"
|
||||
"""Default title for episodes."""
|
||||
EPISODE_TITLE_DATE_FORMAT = "%-d %B %Y"
|
||||
"""Date format in episode title (python's strftime)"""
|
||||
|
||||
# --- Logs & archives
|
||||
LOGS_ARCHIVES_DIR = "logs/archives"
|
||||
"""Directory where to save logs' archives."""
|
||||
|
||||
@property
|
||||
def LOGS_ARCHIVES_DIR_ABS(self):
|
||||
return os.path.join(d_settings.PROJECT_ROOT, self.LOGS_ARCHIVES_DIR)
|
||||
|
||||
LOGS_ARCHIVES_AGE = 60
|
||||
"""In days, minimal age of a log before it is archived."""
|
||||
|
||||
# --- Sounds
|
||||
SOUND_ARCHIVES_SUBDIR = "archives"
|
||||
"""Sub directory used for the complete episode sounds."""
|
||||
SOUND_EXCERPTS_SUBDIR = "excerpts"
|
||||
"""Sub directory used for the excerpts of the episode."""
|
||||
SOUND_QUALITY = {
|
||||
"attribute": "RMS lev dB",
|
||||
"range": (-18.0, -8.0),
|
||||
"sample_length": 120,
|
||||
}
|
||||
"""Quality attributes passed to sound_quality_check from sounds_monitor
|
||||
(Soxi parameters)."""
|
||||
SOUND_FILE_EXT = (".ogg", ".flac", ".wav", ".mp3", ".opus")
|
||||
"""Extension of sound files."""
|
||||
SOUND_KEEP_DELETED = False
|
||||
"""Tag sounds as deleted instead of deleting them when file has been
|
||||
removed from filesystem (sound monitoring)."""
|
||||
|
||||
# --- Streamer & Controllers
|
||||
CONTROLLERS_WORKING_DIR = "/tmp/aircox"
|
||||
"""Controllers working directory."""
|
||||
|
||||
# --- Playlist import from CSV
|
||||
IMPORT_PLAYLIST_CSV_COLS = (
|
||||
"artist",
|
||||
"title",
|
||||
"minutes",
|
||||
"seconds",
|
||||
"tags",
|
||||
"info",
|
||||
)
|
||||
"""Columns for CSV file."""
|
||||
IMPORT_PLAYLIST_CSV_DELIMITER = ";"
|
||||
"""Column delimiter of csv text files."""
|
||||
IMPORT_PLAYLIST_CSV_TEXT_QUOTE = '"'
|
||||
"""Text delimiter of csv text files."""
|
||||
|
||||
|
||||
settings = Settings("AIRCOX")
|
|
@ -9,7 +9,7 @@ from argparse import RawTextHelpFormatter
|
|||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone as tz
|
||||
|
||||
import aircox.settings as settings
|
||||
from aircox.conf import settings
|
||||
from aircox.models import Log
|
||||
from aircox.models.log import LogArchiver
|
||||
|
||||
|
@ -29,9 +29,9 @@ class Command(BaseCommand):
|
|||
"-a",
|
||||
"--age",
|
||||
type=int,
|
||||
default=settings.AIRCOX_LOGS_ARCHIVES_AGE,
|
||||
default=settings.LOGS_ARCHIVES_AGE,
|
||||
help="minimal age in days of logs to archive. Default is "
|
||||
"settings.AIRCOX_LOGS_ARCHIVES_AGE",
|
||||
"settings.LOGS_ARCHIVES_AGE",
|
||||
)
|
||||
group.add_argument(
|
||||
"-k",
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
sound.
|
||||
|
||||
Playlists are in CSV format, where columns are separated with a
|
||||
'{settings.AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER}'. Text quote is
|
||||
{settings.AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE}.
|
||||
The order of the elements is: {settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS}
|
||||
'{settings.IMPORT_PLAYLIST_CSV_DELIMITER}'. Text quote is
|
||||
{settings.IMPORT_PLAYLIST_CSV_TEXT_QUOTE}.
|
||||
The order of the elements is: {settings.IMPORT_PLAYLIST_CSV_COLS}
|
||||
|
||||
If 'minutes' or 'seconds' are given, position will be expressed as timed
|
||||
position, instead of position in playlist.
|
||||
|
@ -16,7 +16,7 @@ from argparse import RawTextHelpFormatter
|
|||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from aircox import settings
|
||||
from aircox.conf import settings
|
||||
from aircox.models import Sound, Track
|
||||
|
||||
__doc__ = __doc__.format(settings=settings)
|
||||
|
@ -61,9 +61,9 @@ class PlaylistImport:
|
|||
)
|
||||
and row.strip()
|
||||
),
|
||||
fieldnames=settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS,
|
||||
delimiter=settings.AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER,
|
||||
quotechar=settings.AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE,
|
||||
fieldnames=settings.IMPORT_PLAYLIST_CSV_COLS,
|
||||
delimiter=settings.IMPORT_PLAYLIST_CSV_DELIMITER,
|
||||
quotechar=settings.IMPORT_PLAYLIST_CSV_TEXT_QUOTE,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -80,7 +80,7 @@ class PlaylistImport:
|
|||
)
|
||||
return
|
||||
|
||||
maps = settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS
|
||||
maps = settings.IMPORT_PLAYLIST_CSV_COLS
|
||||
tracks = []
|
||||
|
||||
logger.info("parse csv file " + self.path)
|
||||
|
|
|
@ -20,7 +20,7 @@ Where:
|
|||
|
||||
|
||||
To check quality of files, call the command sound_quality_check using the
|
||||
parameters given by the setting AIRCOX_SOUND_QUALITY. This script requires
|
||||
parameters given by the setting SOUND_QUALITY. This script requires
|
||||
Sox (and soxi).
|
||||
"""
|
||||
import atexit
|
||||
|
@ -33,7 +33,7 @@ from argparse import RawTextHelpFormatter
|
|||
from django.core.management.base import BaseCommand
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from aircox import settings
|
||||
from aircox.conf import settings
|
||||
from aircox.management.sound_file import SoundFile
|
||||
from aircox.management.sound_monitor import MonitorHandler
|
||||
from aircox.models import Program, Sound
|
||||
|
@ -67,12 +67,12 @@ class Command(BaseCommand):
|
|||
logger.info("#%d %s", program.id, program.title)
|
||||
self.scan_for_program(
|
||||
program,
|
||||
settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
|
||||
settings.SOUND_ARCHIVES_SUBDIR,
|
||||
type=Sound.TYPE_ARCHIVE,
|
||||
)
|
||||
self.scan_for_program(
|
||||
program,
|
||||
settings.AIRCOX_SOUND_EXCERPTS_SUBDIR,
|
||||
settings.SOUND_EXCERPTS_SUBDIR,
|
||||
type=Sound.TYPE_EXCERPT,
|
||||
)
|
||||
dirs.append(os.path.join(program.abspath))
|
||||
|
@ -91,7 +91,7 @@ class Command(BaseCommand):
|
|||
# sounds in directory
|
||||
for path in os.listdir(subdir):
|
||||
path = os.path.join(subdir, path)
|
||||
if not path.endswith(settings.AIRCOX_SOUND_FILE_EXT):
|
||||
if not path.endswith(settings.SOUND_FILE_EXT):
|
||||
continue
|
||||
|
||||
sound_file = SoundFile(path)
|
||||
|
@ -115,12 +115,12 @@ class Command(BaseCommand):
|
|||
"""Run in monitor mode."""
|
||||
with futures.ThreadPoolExecutor() as pool:
|
||||
archives_handler = MonitorHandler(
|
||||
settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
|
||||
settings.SOUND_ARCHIVES_SUBDIR,
|
||||
pool,
|
||||
type=Sound.TYPE_ARCHIVE,
|
||||
)
|
||||
excerpts_handler = MonitorHandler(
|
||||
settings.AIRCOX_SOUND_EXCERPTS_SUBDIR,
|
||||
settings.SOUND_EXCERPTS_SUBDIR,
|
||||
pool,
|
||||
type=Sound.TYPE_EXCERPT,
|
||||
)
|
||||
|
@ -128,12 +128,12 @@ class Command(BaseCommand):
|
|||
observer = Observer()
|
||||
observer.schedule(
|
||||
archives_handler,
|
||||
settings.AIRCOX_PROGRAMS_DIR_ABS,
|
||||
settings.PROGRAMS_DIR_ABS,
|
||||
recursive=True,
|
||||
)
|
||||
observer.schedule(
|
||||
excerpts_handler,
|
||||
settings.AIRCOX_PROGRAMS_DIR_ABS,
|
||||
settings.PROGRAMS_DIR_ABS,
|
||||
recursive=True,
|
||||
)
|
||||
observer.start()
|
||||
|
|
|
@ -17,7 +17,7 @@ Where:
|
|||
Sound Quality
|
||||
=============
|
||||
To check quality of files, call the command sound_quality_check using the
|
||||
parameters given by the setting AIRCOX_SOUND_QUALITY. This script requires
|
||||
parameters given by the setting SOUND_QUALITY. This script requires
|
||||
Sox (and soxi).
|
||||
"""
|
||||
import logging
|
||||
|
@ -175,7 +175,7 @@ class SoundFile:
|
|||
at = tz.datetime(
|
||||
year, month, day, pi.get("hour", 0), pi.get("minute", 0)
|
||||
)
|
||||
at = tz.get_current_timezone().localize(at)
|
||||
at = tz.make_aware(at)
|
||||
else:
|
||||
at = date(year, month, day)
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ Where:
|
|||
|
||||
|
||||
To check quality of files, call the command sound_quality_check using the
|
||||
parameters given by the setting AIRCOX_SOUND_QUALITY. This script requires
|
||||
parameters given by the setting SOUND_QUALITY. This script requires
|
||||
Sox (and soxi).
|
||||
"""
|
||||
import logging
|
||||
|
@ -29,7 +29,7 @@ from datetime import datetime, timedelta
|
|||
|
||||
from watchdog.events import PatternMatchingEventHandler
|
||||
|
||||
from aircox import settings
|
||||
from aircox.conf import settings
|
||||
from aircox.models import Sound
|
||||
|
||||
from .sound_file import SoundFile
|
||||
|
@ -121,7 +121,7 @@ class MonitorHandler(PatternMatchingEventHandler):
|
|||
def __init__(self, subdir, pool, **sync_kw):
|
||||
"""
|
||||
:param str subdir: sub-directory in program dirs to monitor \
|
||||
(AIRCOX_SOUND_ARCHIVES_SUBDIR or AIRCOX_SOUND_EXCERPTS_SUBDIR);
|
||||
(SOUND_ARCHIVES_SUBDIR or SOUND_EXCERPTS_SUBDIR);
|
||||
:param concurrent.futures.Executor pool: pool executing jobs on file
|
||||
change;
|
||||
:param **sync_kw: kwargs passed to `SoundFile.sync`;
|
||||
|
@ -132,7 +132,7 @@ class MonitorHandler(PatternMatchingEventHandler):
|
|||
|
||||
patterns = [
|
||||
"*/{}/*{}".format(self.subdir, ext)
|
||||
for ext in settings.AIRCOX_SOUND_FILE_EXT
|
||||
for ext in settings.SOUND_FILE_EXT
|
||||
]
|
||||
super().__init__(patterns=patterns, ignore_directories=True)
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from . import signals
|
||||
from .article import Article
|
||||
from .episode import Diffusion, DiffusionQuerySet, Episode
|
||||
from .log import Log, LogArchiver, LogQuerySet
|
||||
|
|
|
@ -7,7 +7,8 @@ from django.utils.functional import cached_property
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from easy_thumbnails.files import get_thumbnailer
|
||||
|
||||
from aircox import settings, utils
|
||||
from aircox.conf import settings
|
||||
from aircox import utils
|
||||
|
||||
from .page import Page
|
||||
from .program import (
|
||||
|
@ -70,18 +71,18 @@ class Episode(Page):
|
|||
|
||||
@classmethod
|
||||
def get_default_title(cls, page, date):
|
||||
return settings.AIRCOX_EPISODE_TITLE.format(
|
||||
return settings.EPISODE_TITLE.format(
|
||||
program=page,
|
||||
date=date.strftime(settings.AIRCOX_EPISODE_TITLE_DATE_FORMAT),
|
||||
date=date.strftime(settings.EPISODE_TITLE_DATE_FORMAT),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_init_kwargs_from(cls, page, date, title=None, **kwargs):
|
||||
"""Get default Episode's title."""
|
||||
title = (
|
||||
settings.AIRCOX_EPISODE_TITLE.format(
|
||||
settings.EPISODE_TITLE.format(
|
||||
program=page,
|
||||
date=date.strftime(settings.AIRCOX_EPISODE_TITLE_DATE_FORMAT),
|
||||
date=date.strftime(settings.EPISODE_TITLE_DATE_FORMAT),
|
||||
)
|
||||
if title is None
|
||||
else title
|
||||
|
|
|
@ -10,8 +10,9 @@ from django.utils import timezone as tz
|
|||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from aircox import settings
|
||||
from aircox.conf import settings
|
||||
|
||||
__all__ = ("Settings", "settings")
|
||||
from .episode import Diffusion
|
||||
from .sound import Sound, Track
|
||||
from .station import Station
|
||||
|
@ -251,7 +252,7 @@ class LogArchiver:
|
|||
@staticmethod
|
||||
def get_path(station, date):
|
||||
return os.path.join(
|
||||
settings.AIRCOX_LOGS_ARCHIVES_DIR,
|
||||
settings.LOGS_ARCHIVES_DIR_ABS,
|
||||
"{}_{}.log.gz".format(date.strftime("%Y%m%d"), station.pk),
|
||||
)
|
||||
|
||||
|
@ -264,7 +265,7 @@ class LogArchiver:
|
|||
if not qs.exists():
|
||||
return 0
|
||||
|
||||
os.makedirs(settings.AIRCOX_LOGS_ARCHIVES_DIR, exist_ok=True)
|
||||
os.makedirs(settings.LOGS_ARCHIVES_DIR_ABS, exist_ok=True)
|
||||
count = qs.count()
|
||||
logs = self.sort_logs(qs)
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ from django.utils import timezone as tz
|
|||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from aircox import settings, utils
|
||||
from aircox import utils
|
||||
from aircox.conf import settings
|
||||
|
||||
from .page import Page, PageQuerySet
|
||||
from .station import Station
|
||||
|
@ -77,9 +78,7 @@ class Program(Page):
|
|||
@property
|
||||
def path(self):
|
||||
"""Return program's directory path."""
|
||||
return os.path.join(
|
||||
settings.AIRCOX_PROGRAMS_DIR, self.slug.replace("-", "_")
|
||||
)
|
||||
return os.path.join(settings.PROGRAMS_DIR, self.slug.replace("-", "_"))
|
||||
|
||||
@property
|
||||
def abspath(self):
|
||||
|
@ -88,11 +87,11 @@ class Program(Page):
|
|||
|
||||
@property
|
||||
def archives_path(self):
|
||||
return os.path.join(self.path, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR)
|
||||
return os.path.join(self.path, settings.SOUND_ARCHIVES_SUBDIR)
|
||||
|
||||
@property
|
||||
def excerpts_path(self):
|
||||
return os.path.join(self.path, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR)
|
||||
return os.path.join(self.path, settings.SOUND_ARCHIVES_SUBDIR)
|
||||
|
||||
def __init__(self, *kargs, **kwargs):
|
||||
super().__init__(*kargs, **kwargs)
|
||||
|
@ -107,8 +106,8 @@ class Program(Page):
|
|||
We assume the path has been given in a previous time by this
|
||||
model (Program.path getter).
|
||||
"""
|
||||
if path.startswith(settings.AIRCOX_PROGRAMS_DIR_ABS):
|
||||
path = path.replace(settings.AIRCOX_PROGRAMS_DIR_ABS, "")
|
||||
if path.startswith(settings.PROGRAMS_DIR_ABS):
|
||||
path = path.replace(settings.PROGRAMS_DIR_ABS, "")
|
||||
while path[0] == "/":
|
||||
path = path[1:]
|
||||
path = path[: path.index("/")]
|
||||
|
|
|
@ -4,7 +4,8 @@ from django.db.models import signals
|
|||
from django.dispatch import receiver
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from .. import settings, utils
|
||||
from aircox import utils
|
||||
from aircox.conf import settings
|
||||
from . import Diffusion, Episode, Page, Program, Schedule
|
||||
|
||||
|
||||
|
@ -20,7 +21,7 @@ def user_default_groups(sender, instance, created, *args, **kwargs):
|
|||
if not created or instance.is_superuser:
|
||||
return
|
||||
|
||||
for group_name, permissions in settings.AIRCOX_DEFAULT_USER_GROUPS.items():
|
||||
for group_name, permissions in settings.DEFAULT_USER_GROUPS.items():
|
||||
if instance.groups.filter(name=group_name).count():
|
||||
continue
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.utils import timezone as tz
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from aircox import settings
|
||||
from aircox.conf import settings
|
||||
|
||||
from .episode import Episode
|
||||
from .program import Program
|
||||
|
@ -123,9 +123,9 @@ class Sound(models.Model):
|
|||
|
||||
def _upload_to(self, filename):
|
||||
subdir = (
|
||||
settings.AIRCOX_SOUND_ARCHIVES_SUBDIR
|
||||
settings.SOUND_ARCHIVES_SUBDIR
|
||||
if self.type == self.TYPE_ARCHIVE
|
||||
else settings.AIRCOX_SOUND_EXCERPTS_SUBDIR
|
||||
else settings.SOUND_EXCERPTS_SUBDIR
|
||||
)
|
||||
return os.path.join(self.program.path, subdir, filename)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.functional import cached_property
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from filer.fields.image import FilerImageField
|
||||
|
||||
from .. import settings
|
||||
from aircox.conf import settings
|
||||
|
||||
__all__ = ("Station", "StationQuerySet", "Port")
|
||||
|
||||
|
@ -92,7 +92,7 @@ class Station(models.Model):
|
|||
def save(self, make_sources=True, *args, **kwargs):
|
||||
if not self.path:
|
||||
self.path = os.path.join(
|
||||
settings.AIRCOX_CONTROLLERS_WORKING_DIR,
|
||||
settings.CONTROLLERS_WORKING_DIR,
|
||||
self.slug.replace("-", "_"),
|
||||
)
|
||||
|
||||
|
|
168
aircox/settings.py
Executable file → Normal file
168
aircox/settings.py
Executable file → Normal file
|
@ -1,125 +1,73 @@
|
|||
import os
|
||||
import inspect
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf import settings as django_settings
|
||||
from django.db import models
|
||||
|
||||
|
||||
def ensure(key, default):
|
||||
value = getattr(settings, key, default)
|
||||
globals()[key] = value
|
||||
return value
|
||||
class Settings:
|
||||
"""Utility class used to load and save settings, can be used as model.
|
||||
|
||||
Some members are excluded from being configuration:
|
||||
- Protected/private members;
|
||||
- On django model, "objects" and "Meta";
|
||||
- Class declaration and callables
|
||||
|
||||
########################################################################
|
||||
# Global & misc
|
||||
########################################################################
|
||||
# group to assign to users at their creation, along with the permissions
|
||||
# to add to each group.
|
||||
ensure(
|
||||
"AIRCOX_DEFAULT_USER_GROUPS",
|
||||
{
|
||||
"radio hosts": (
|
||||
# TODO include content_type in order to avoid clash with potential
|
||||
# extra applications
|
||||
# aircox
|
||||
"change_program",
|
||||
"change_episode",
|
||||
"change_diffusion",
|
||||
"add_comment",
|
||||
"change_comment",
|
||||
"delete_comment",
|
||||
"add_article",
|
||||
"change_article",
|
||||
"delete_article",
|
||||
"change_sound",
|
||||
"add_track",
|
||||
"change_track",
|
||||
"delete_track",
|
||||
# taggit
|
||||
"add_tag",
|
||||
"change_tag",
|
||||
"delete_tag",
|
||||
# filer
|
||||
"add_folder",
|
||||
"change_folder",
|
||||
"delete_folder",
|
||||
"can_use_directory_listing",
|
||||
"add_image",
|
||||
"change_image",
|
||||
"delete_image",
|
||||
),
|
||||
},
|
||||
)
|
||||
Example:
|
||||
|
||||
# Directory for the programs data
|
||||
AIRCOX_PROGRAMS_DIR = ensure("AIRCOX_PROGRAMS_DIR", "programs")
|
||||
ensure(
|
||||
"AIRCOX_PROGRAMS_DIR_ABS",
|
||||
os.path.join(settings.MEDIA_ROOT, AIRCOX_PROGRAMS_DIR),
|
||||
)
|
||||
```
|
||||
class MySettings(Settings):
|
||||
a = 13
|
||||
b = 12
|
||||
|
||||
my_settings = MySettings().load('MY_SETTINGS_KEY')
|
||||
print(my_settings.a, my_settings.get('b'))
|
||||
```
|
||||
|
||||
########################################################################
|
||||
# Programs & Episodes
|
||||
########################################################################
|
||||
# default title for episodes
|
||||
ensure("AIRCOX_EPISODE_TITLE", "{program.title} - {date}")
|
||||
# date format in episode title (python's strftime)
|
||||
ensure("AIRCOX_EPISODE_TITLE_DATE_FORMAT", "%-d %B %Y")
|
||||
This will load values from django project settings.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
# Logs & Archives
|
||||
########################################################################
|
||||
# Directory where to save logs' archives
|
||||
ensure(
|
||||
"AIRCOX_LOGS_ARCHIVES_DIR",
|
||||
os.path.join(settings.PROJECT_ROOT, "logs/archives"),
|
||||
)
|
||||
# In days, minimal age of a log before it is archived
|
||||
ensure("AIRCOX_LOGS_ARCHIVES_AGE", 60)
|
||||
def load(self, key, module=None):
|
||||
"""Load settings from module's item specified by its member name. When
|
||||
no module is provided, uses ``django.conf.settings``.
|
||||
|
||||
:param str key: module member name.
|
||||
:param module: configuration object.
|
||||
:returns self
|
||||
"""
|
||||
if module is None:
|
||||
module = django_settings
|
||||
settings = getattr(module, key, None)
|
||||
if settings:
|
||||
self.update(settings)
|
||||
return self
|
||||
|
||||
########################################################################
|
||||
# Sounds
|
||||
########################################################################
|
||||
# Sub directory used for the complete episode sounds
|
||||
ensure("AIRCOX_SOUND_ARCHIVES_SUBDIR", "archives")
|
||||
# Sub directory used for the excerpts of the episode
|
||||
ensure("AIRCOX_SOUND_EXCERPTS_SUBDIR", "excerpts")
|
||||
def update(self, settings):
|
||||
"""Update self's values from provided settings. ``settings`` can be an
|
||||
iterable of ``(key, value)``.
|
||||
|
||||
# Quality attributes passed to sound_quality_check from sounds_monitor
|
||||
ensure(
|
||||
"AIRCOX_SOUND_QUALITY",
|
||||
{
|
||||
"attribute": "RMS lev dB",
|
||||
"range": (-18.0, -8.0),
|
||||
"sample_length": 120,
|
||||
},
|
||||
)
|
||||
:param dict|Settings|iterable settings: value to update from.
|
||||
"""
|
||||
if isinstance(settings, (dict, Settings)):
|
||||
settings = settings.items()
|
||||
for key, value in settings:
|
||||
if hasattr(self, key) and self.is_config_item(key, value):
|
||||
setattr(self, key, value)
|
||||
|
||||
# Extension of sound files
|
||||
ensure("AIRCOX_SOUND_FILE_EXT", (".ogg", ".flac", ".wav", ".mp3", ".opus"))
|
||||
def get(self, key, default=None):
|
||||
"""Return settings' value for provided key."""
|
||||
return getattr(self, key, default)
|
||||
|
||||
# Tag sounds as deleted instead of deleting them when file has been removed
|
||||
# from filesystem (sound monitoring)
|
||||
ensure("AIRCOX_SOUND_KEEP_DELETED", False)
|
||||
def items(self):
|
||||
"""Iterate over items members, as tupple of ``key, value``."""
|
||||
for key in dir(self):
|
||||
value = getattr(self, key)
|
||||
if self.is_config_item(key, value):
|
||||
yield key, value
|
||||
|
||||
|
||||
########################################################################
|
||||
# Streamer & Controllers
|
||||
########################################################################
|
||||
# Controllers working directory
|
||||
ensure("AIRCOX_CONTROLLERS_WORKING_DIR", "/tmp/aircox")
|
||||
|
||||
|
||||
########################################################################
|
||||
# Playlist import from CSV
|
||||
########################################################################
|
||||
# Columns for CSV file
|
||||
ensure(
|
||||
"AIRCOX_IMPORT_PLAYLIST_CSV_COLS",
|
||||
("artist", "title", "minutes", "seconds", "tags", "info"),
|
||||
)
|
||||
# Column delimiter of csv text files
|
||||
ensure("AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER", ";")
|
||||
# Text delimiter of csv text files
|
||||
ensure("AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE", '"')
|
||||
def is_config_item(self, key, value):
|
||||
"""Return True if key/value item is a configuration setting."""
|
||||
if key.startswith("_") or callable(value) or inspect.isclass(value):
|
||||
return False
|
||||
if isinstance(self, models.Model) and key == "object":
|
||||
return False
|
||||
return True
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
from .sound_file import SoundFileTestCase
|
||||
from .sound_monitor import (
|
||||
ModifiedHandlerTestCase,
|
||||
MonitorHandlerTestCase,
|
||||
MoveHandlerTestCase,
|
||||
NotifyHandlerTestCase,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
"SoundFileTestCase",
|
||||
"NotifyHandlerTestCase",
|
||||
"MoveHandlerTestCase",
|
||||
"ModifiedHandlerTestCase",
|
||||
"MonitorHandlerTestCase",
|
||||
)
|
|
@ -1,103 +0,0 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings as conf
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from aircox import models
|
||||
from aircox.management.sound_file import SoundFile
|
||||
|
||||
__all__ = ("SoundFileTestCase",)
|
||||
|
||||
|
||||
class SoundFileTestCase(TestCase):
|
||||
path_infos = {
|
||||
"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,
|
||||
"name": "Sample 2",
|
||||
},
|
||||
"test/20220103_1_sample_3.mp3": {
|
||||
"year": 2022,
|
||||
"month": 1,
|
||||
"day": 3,
|
||||
"n": 1,
|
||||
"name": "Sample 3",
|
||||
},
|
||||
"test/20220104_sample_4.mp3": {
|
||||
"year": 2022,
|
||||
"month": 1,
|
||||
"day": 4,
|
||||
"name": "Sample 4",
|
||||
},
|
||||
"test/20220105.mp3": {
|
||||
"year": 2022,
|
||||
"month": 1,
|
||||
"day": 5,
|
||||
"name": "20220105",
|
||||
},
|
||||
}
|
||||
subdir_prefix = "test"
|
||||
sound_files = {
|
||||
k: r
|
||||
for k, r in (
|
||||
(path, SoundFile(conf.MEDIA_ROOT + "/" + path))
|
||||
for path in path_infos.keys()
|
||||
)
|
||||
}
|
||||
|
||||
def test_sound_path(self):
|
||||
for path, sound_file in self.sound_files.items():
|
||||
self.assertEqual(path, sound_file.sound_path)
|
||||
|
||||
def test_read_path(self):
|
||||
for path, sound_file in self.sound_files.items():
|
||||
expected = self.path_infos[path]
|
||||
result = sound_file.read_path(path)
|
||||
# remove None values
|
||||
result = {k: v for k, v in result.items() if v is not None}
|
||||
self.assertEqual(expected, result, "path: {}".format(path))
|
||||
|
||||
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
|
||||
|
||||
def test_find_episode(self):
|
||||
station = models.Station(name="test-station")
|
||||
program = models.Program(station=station, title="test")
|
||||
station.save()
|
||||
program.save()
|
||||
|
||||
for path, sound_file in self.sound_files.items():
|
||||
infos = sound_file.read_path(path)
|
||||
diff = self._setup_diff(program, infos)
|
||||
sound = models.Sound(program=diff.program, file=path)
|
||||
result = sound_file.find_episode(sound, infos)
|
||||
self.assertEquals(diff.episode, result)
|
||||
|
||||
# TODO: find_playlist, sync
|
111
aircox/tests/management/test_sound_file.py
Normal file
111
aircox/tests/management/test_sound_file.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import pytest
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings as conf
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from aircox import models
|
||||
from aircox.management.sound_file import SoundFile
|
||||
|
||||
|
||||
@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,
|
||||
"name": "Sample 2",
|
||||
},
|
||||
"test/20220103_1_sample_3.mp3": {
|
||||
"year": 2022,
|
||||
"month": 1,
|
||||
"day": 3,
|
||||
"n": 1,
|
||||
"name": "Sample 3",
|
||||
},
|
||||
"test/20220104_sample_4.mp3": {
|
||||
"year": 2022,
|
||||
"month": 1,
|
||||
"day": 4,
|
||||
"name": "Sample 4",
|
||||
},
|
||||
"test/20220105.mp3": {
|
||||
"year": 2022,
|
||||
"month": 1,
|
||||
"day": 5,
|
||||
"name": "20220105",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sound_files(path_infos):
|
||||
return {
|
||||
k: r
|
||||
for k, r in (
|
||||
(path, SoundFile(conf.MEDIA_ROOT + "/" + path))
|
||||
for path in path_infos.keys()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def test_sound_path(sound_files):
|
||||
for path, sound_file in sound_files.items():
|
||||
assert path == sound_file.sound_path
|
||||
|
||||
|
||||
def test_read_path(path_infos, sound_files):
|
||||
for path, sound_file in sound_files.items():
|
||||
expected = path_infos[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 _setup_diff(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(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
|
|
@ -1,22 +1,15 @@
|
|||
import pytest
|
||||
|
||||
import concurrent.futures as futures
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from aircox.management.sound_monitor import (
|
||||
ModifiedHandler,
|
||||
MonitorHandler,
|
||||
NotifyHandler,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
"NotifyHandlerTestCase",
|
||||
"MoveHandlerTestCase",
|
||||
"ModifiedHandlerTestCase",
|
||||
"MonitorHandlerTestCase",
|
||||
)
|
||||
|
||||
|
||||
class FakeEvent:
|
||||
def __init__(self, **kwargs):
|
||||
|
@ -31,22 +24,28 @@ class WaitHandler(NotifyHandler):
|
|||
pass
|
||||
|
||||
|
||||
class NotifyHandlerTestCase(TestCase):
|
||||
@pytest.fixture
|
||||
def monitor():
|
||||
pool = futures.ThreadPoolExecutor(2)
|
||||
return MonitorHandler("archives", pool)
|
||||
|
||||
|
||||
class TestNotifyHandler:
|
||||
pass
|
||||
|
||||
|
||||
class MoveHandlerTestCase(TestCase):
|
||||
class TestMoveHandler:
|
||||
pass
|
||||
|
||||
|
||||
class ModifiedHandlerTestCase(TestCase):
|
||||
class TestModifiedHandler:
|
||||
def test_wait(self):
|
||||
handler = ModifiedHandler()
|
||||
handler.timeout_delta = timedelta(seconds=0.1)
|
||||
start = datetime.now()
|
||||
handler.wait()
|
||||
delta = datetime.now() - start
|
||||
self.assertTrue(delta < handler.timeout_delta + timedelta(seconds=0.1))
|
||||
assert delta < handler.timeout_delta + timedelta(seconds=0.1)
|
||||
|
||||
def test_wait_ping(self):
|
||||
pool = futures.ThreadPoolExecutor(1)
|
||||
|
@ -57,28 +56,24 @@ class ModifiedHandlerTestCase(TestCase):
|
|||
time.sleep(0.3)
|
||||
handler.ping()
|
||||
time.sleep(0.3)
|
||||
self.assertTrue(future.running())
|
||||
assert future.running()
|
||||
|
||||
|
||||
class MonitorHandlerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
pool = futures.ThreadPoolExecutor(2)
|
||||
self.monitor = MonitorHandler("archives", pool)
|
||||
|
||||
def test_submit_new_job(self):
|
||||
class TestMonitorHandler:
|
||||
def test_submit_new_job(self, monitor):
|
||||
event = FakeEvent(src_path="dummy_src")
|
||||
handler = NotifyHandler()
|
||||
result, _ = self.monitor._submit(handler, event, "up")
|
||||
self.assertIs(handler, result)
|
||||
self.assertIsInstance(handler.future, futures.Future)
|
||||
self.monitor.pool.shutdown()
|
||||
result, _ = monitor._submit(handler, event, "up")
|
||||
assert handler == result
|
||||
assert isinstance(handler.future, futures.Future)
|
||||
monitor.pool.shutdown()
|
||||
|
||||
def test_submit_job_exists(self):
|
||||
def test_submit_job_exists(self, monitor):
|
||||
event = FakeEvent(src_path="dummy_src")
|
||||
|
||||
job_1, new_1 = self.monitor._submit(WaitHandler(), event, "up")
|
||||
job_2, new_2 = self.monitor._submit(NotifyHandler(), event, "up")
|
||||
self.assertIs(job_1, job_2)
|
||||
self.assertTrue(new_1)
|
||||
self.assertFalse(new_2)
|
||||
self.monitor.pool.shutdown()
|
||||
job_1, new_1 = monitor._submit(WaitHandler(), event, "up")
|
||||
job_2, new_2 = monitor._submit(NotifyHandler(), event, "up")
|
||||
assert job_1 == job_2
|
||||
assert new_1
|
||||
assert not new_2
|
||||
monitor.pool.shutdown()
|
|
@ -1,8 +1,7 @@
|
|||
import datetime
|
||||
|
||||
import django.utils.timezone as tz
|
||||
|
||||
__all__ = [
|
||||
__all__ = (
|
||||
"Redirect",
|
||||
"redirect",
|
||||
"date_range",
|
||||
|
@ -10,9 +9,10 @@ __all__ = [
|
|||
"date_or_default",
|
||||
"to_timedelta",
|
||||
"seconds_to_time",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# FIXME: usage & why we don't use Django's
|
||||
class Redirect(Exception):
|
||||
"""Redirect exception -- see `redirect()`."""
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.template.loader import render_to_string
|
|||
from django.utils import timezone as tz
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from aircox import settings
|
||||
from aircox.conf import settings
|
||||
from aircox.utils import to_seconds
|
||||
|
||||
from .connector import Connector
|
||||
|
|
0
instance/settings/__init__.py
Normal file
0
instance/settings/__init__.py
Normal file
|
@ -1,15 +1,3 @@
|
|||
"""Django and Aircox instance settings. This file should be saved as
|
||||
`settings.py` in the same directory as this one.
|
||||
|
||||
User MUST define the following values: `SECRET_KEY`, `ALLOWED_HOSTS`, `DATABASES`
|
||||
|
||||
The following environment variables are used in settings:
|
||||
* `AIRCOX_DEBUG` (`DEBUG`): enable/disable debugging
|
||||
|
||||
For Django settings see:
|
||||
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
@ -19,7 +7,8 @@ from django.utils import timezone
|
|||
sys.path.insert(1, os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
# Project root directory
|
||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
|
||||
PROJECT_ROOT = os.path.abspath(__file__ + "/../../../")
|
||||
|
||||
# DEBUG mode
|
||||
DEBUG = (
|
||||
(os.environ["AIRCOX_DEBUG"].lower() in ("true", 1))
|
||||
|
@ -38,12 +27,6 @@ LC_LOCALE = "en_US.UTF-8"
|
|||
TIME_ZONE = os.environ.get("TZ") or "UTC"
|
||||
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# You MUST configure those values
|
||||
#
|
||||
########################################################################
|
||||
|
||||
# Secret key: you MUST put a consistent secret key. You can generate one
|
||||
# at https://djecrety.ir/
|
||||
SECRET_KEY = ""
|
||||
|
@ -56,15 +39,11 @@ DATABASES = {
|
|||
"TIMEZONE": TIME_ZONE,
|
||||
}
|
||||
}
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
# Allowed host for HTTP requests
|
||||
ALLOWED_HOSTS = ("127.0.0.1",)
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# You CAN configure starting from here
|
||||
#
|
||||
########################################################################
|
||||
|
||||
# Assets and medias:
|
||||
# In production, user MUST configure webserver in order to serve static
|
||||
|
@ -81,26 +60,6 @@ STATIC_ROOT = os.path.join(PROJECT_ROOT, "static")
|
|||
# Path to media directory (by default in static's directory)
|
||||
MEDIA_ROOT = os.path.join(STATIC_ROOT, "media")
|
||||
|
||||
# Include specific configuration depending of DEBUG
|
||||
if DEBUG:
|
||||
from .dev import *
|
||||
else:
|
||||
from .prod import *
|
||||
|
||||
# Enable caching using memcache
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"LOCATION": "127.0.0.1:11211",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# You don't really need to configure what is happening below
|
||||
#
|
||||
########################################################################
|
||||
# Enables internationalization and timezone
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
|
@ -112,7 +71,7 @@ try:
|
|||
import locale
|
||||
|
||||
locale.setlocale(locale.LC_ALL, LC_LOCALE)
|
||||
except:
|
||||
except Exception:
|
||||
print(
|
||||
"Can not set locale {LC}. Is it available on you system? Hint: "
|
||||
"Check /etc/locale.gen and rerun locale-gen as sudo if needed.".format(
|
|
@ -1,5 +1,13 @@
|
|||
import os
|
||||
|
||||
from .base import *
|
||||
|
||||
try:
|
||||
from .settings import *
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
LOCALE_PATHS = ["aircox/locale", "aircox_streamer/locale"]
|
||||
|
||||
LOGGING = {
|
|
@ -1,5 +1,10 @@
|
|||
import os
|
||||
|
||||
try:
|
||||
from .settings import *
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
|
@ -30,3 +35,12 @@ LOGGING = {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# Enable caching using memcache
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"LOCATION": "127.0.0.1:11211",
|
||||
}
|
||||
}
|
43
instance/settings/sample.py
Normal file
43
instance/settings/sample.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
"""Django and Aircox instance settings. This file should be saved as
|
||||
`settings.py` in the same directory as this one.
|
||||
|
||||
User MUST define the following values: `SECRET_KEY`, `ALLOWED_HOSTS`, `DATABASES`
|
||||
|
||||
The following environment variables are used in settings:
|
||||
* `AIRCOX_DEBUG` (`DEBUG`): enable/disable debugging
|
||||
|
||||
For Django settings see:
|
||||
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||
"""
|
||||
|
||||
# Debug mode: set to True for dev
|
||||
# DEBUG = False
|
||||
LANGUAGE_CODE = "fr-BE"
|
||||
LC_LOCALE = "fr_BE.UTF-8"
|
||||
|
||||
# Secret key: you MUST put a consistent secret key. You can generate one
|
||||
# at https://djecrety.ir/
|
||||
SECRET_KEY = ""
|
||||
|
||||
# Database configuration: defaults to db.sqlite3
|
||||
# DATABASES
|
||||
|
||||
# Allowed host for HTTP requests
|
||||
# ALLOWED_HOSTS = ('127.0.0.1',)
|
||||
|
||||
|
||||
# When LC_LOCALE is set here, this code activates it.
|
||||
# Otherwise it can be removed
|
||||
try:
|
||||
import locale
|
||||
|
||||
locale.setlocale(locale.LC_ALL, LC_LOCALE)
|
||||
except Exception:
|
||||
print(
|
||||
"Can not set locale {LC}. Is it available on you system? Hint: "
|
||||
"Check /etc/locale.gen and rerun locale-gen as sudo if needed.".format(
|
||||
LC=LANGUAGE_CODE
|
||||
)
|
||||
)
|
||||
pass
|
4
pytest.ini
Normal file
4
pytest.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = instance.settings
|
||||
# -- recommended but optional:
|
||||
python_files = tests.py test_*.py *_tests.py
|
|
@ -3,6 +3,7 @@ djangorestframework~=3.13
|
|||
django-model-utils>=4.2
|
||||
django-filter~=22.1
|
||||
|
||||
django-content-editor~=6.3
|
||||
django-filer~=2.2
|
||||
django-honeypot~=1.0
|
||||
django-taggit~=3.0
|
||||
|
|
2
requirements_tests.txt
Normal file
2
requirements_tests.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
pytest~=7.2
|
||||
pytest-django~=4.5
|
|
@ -1,4 +1,5 @@
|
|||
#! /bin/sh
|
||||
export DJANGO_SETTINGS_MODULE=instance.settings.prod
|
||||
|
||||
# aircox daily tasks:
|
||||
# - diffusions monitoring for the current month
|
||||
|
|
|
@ -21,7 +21,7 @@ autostart = true
|
|||
autorestart = true
|
||||
stdout_logfile = /srv/www/aircox/logs/server.log
|
||||
redirect_stderr = true
|
||||
environment=AIRCOX_DEBUG="False"
|
||||
environment=AIRCOX_DEBUG="False",DJANGO_SETTINGS_MODULE=instance.settings.prod
|
||||
|
||||
[program:aircox_sounds_monitor]
|
||||
command = /srv/www/aircox/scripts/launch_in_venv ./manage.py sounds_monitor -qsm
|
||||
|
@ -31,7 +31,7 @@ autostart = true
|
|||
autorestart = true
|
||||
stdout_logfile = /srv/www/aircox/logs/sounds_monitor.log
|
||||
redirect_stderr = true
|
||||
environment=AIRCOX_DEBUG="False"
|
||||
environment=AIRCOX_DEBUG="False",DJANGO_SETTINGS_MODULE=instance.settings.prod
|
||||
|
||||
[program:aircox_streamer]
|
||||
command = /srv/www/aircox/scripts/launch_in_venv ./manage.py streamer -crm
|
||||
|
@ -41,4 +41,4 @@ autostart = true
|
|||
autorestart = true
|
||||
stdout_logfile = /srv/www/aircox/logs/streamer.log
|
||||
redirect_stderr = true
|
||||
environment=AIRCOX_DEBUG="False"
|
||||
environment=AIRCOX_DEBUG="False",DJANGO_SETTINGS_MODULE=instance.settings.prod
|
||||
|
|
Loading…
Reference in New Issue
Block a user