feat: move timezone preference to config file (#2096)

BREAKING CHANGE: The timezone preference moved to the configuration
file.
This commit is contained in:
Jonas L 2022-09-14 12:48:08 +02:00 committed by GitHub
parent 8ef82d798e
commit 9b3207b8a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 73 additions and 15 deletions

View File

@ -6,7 +6,6 @@ ANALYZE "cc_pref";
INSERT INTO cc_live_log ("state", "start_time") VALUES ('S', now() at time zone 'UTC'); INSERT INTO cc_live_log ("state", "start_time") VALUES ('S', now() at time zone 'UTC');
INSERT INTO cc_pref ("keystr", "valstr") VALUES ('import_timestamp', '0'); INSERT INTO cc_pref ("keystr", "valstr") VALUES ('import_timestamp', '0');
INSERT INTO cc_pref ("keystr", "valstr") VALUES ('timezone', 'UTC');
INSERT INTO cc_pref ("keystr", "valstr") VALUES ('off_air_meta', 'LibreTime - offline'); INSERT INTO cc_pref ("keystr", "valstr") VALUES ('off_air_meta', 'LibreTime - offline');
INSERT INTO cc_pref ("keystr", "valstr") VALUES ('enable_replay_gain', 1); INSERT INTO cc_pref ("keystr", "valstr") VALUES ('enable_replay_gain', 1);
INSERT INTO cc_pref ("keystr", "valstr") VALUES ('locale', 'en_US'); INSERT INTO cc_pref ("keystr", "valstr") VALUES ('locale', 'en_US');

View File

@ -13,6 +13,11 @@ general:
# > default is [] # > default is []
allowed_cors_origins: [] allowed_cors_origins: []
# The server timezone, should be a lookup key in the IANA time zone database,
# for example Europe/Berlin.
# > default is UTC
timezone: UTC
# How many hours ahead Playout should cache scheduled media files. # How many hours ahead Playout should cache scheduled media files.
# > default is 1 # > default is 1
cache_ahead_hours: 1 cache_ahead_hours: 1

View File

@ -13,6 +13,11 @@ general:
# > default is [] # > default is []
allowed_cors_origins: [] allowed_cors_origins: []
# The server timezone, should be a lookup key in the IANA time zone database,
# for example Europe/Berlin.
# > default is UTC
timezone: UTC
# How many hours ahead Playout should cache scheduled media files. # How many hours ahead Playout should cache scheduled media files.
# > default is 1 # > default is 1
cache_ahead_hours: 1 cache_ahead_hours: 1

View File

@ -29,6 +29,11 @@ general:
# > default is [] # > default is []
allowed_cors_origins: [] allowed_cors_origins: []
# The server timezone, should be a lookup key in the IANA time zone database,
# for example Europe/Berlin.
# > default is UTC
timezone: UTC
# How many hours ahead Playout should cache scheduled media files. # How many hours ahead Playout should cache scheduled media files.
# > default is 1 # > default is 1
cache_ahead_hours: 1 cache_ahead_hours: 1

View File

@ -74,6 +74,20 @@ sudo -u libretime libretime-api dbshell --command="
::: :::
### Timezone configuration
The timezone preference moved from the database to the [configuration](../admin-manual/setup/configuration.md#general) file. Make sure to save your existing timezone preference to the configuration file.
:::info
To prevent accidental data loss during upgrade, the timezone preference will only be removed from the database in future releases. You can view the data using the following commands:
```bash
sudo -u libretime libretime-api dbshell --command="SELECT * FROM cc_pref WHERE keystr = 'timezone'";
```
:::
## :arrow_up: Upgrading ## :arrow_up: Upgrading
### Worker python package and service ### Worker python package and service

View File

@ -13,6 +13,11 @@ general:
# > default is [] # > default is []
allowed_cors_origins: [] allowed_cors_origins: []
# The server timezone, should be a lookup key in the IANA time zone database,
# for example Europe/Berlin.
# > default is UTC
timezone: UTC
# How many hours ahead Playout should cache scheduled media files. # How many hours ahead Playout should cache scheduled media files.
# > default is 1 # > default is 1
cache_ahead_hours: 1 cache_ahead_hours: 1

View File

@ -36,6 +36,11 @@ class Schema implements ConfigurationInterface
/**/->scalarNode('public_url')->cannotBeEmpty()->end() /**/->scalarNode('public_url')->cannotBeEmpty()->end()
/**/->scalarNode('api_key')->cannotBeEmpty()->end() /**/->scalarNode('api_key')->cannotBeEmpty()->end()
/**/->arrayNode('allowed_cors_origins')->scalarPrototype()->defaultValue([])->end()->end() /**/->arrayNode('allowed_cors_origins')->scalarPrototype()->defaultValue([])->end()->end()
/**/->scalarNode('timezone')->cannotBeEmpty()->defaultValue("UTC")
/* */->validate()->ifNotInArray(DateTimeZone::listIdentifiers())
/* */->thenInvalid('invalid general.timezone %s')
/* */->end()
/**/->end()
/**/->scalarNode('dev_env')->defaultValue('production')->end() /**/->scalarNode('dev_env')->defaultValue('production')->end()
/**/->scalarNode('auth')->defaultValue('local')->end() /**/->scalarNode('auth')->defaultValue('local')->end()
/**/->integerNode('cache_ahead_hours')->defaultValue(1)->end() /**/->integerNode('cache_ahead_hours')->defaultValue(1)->end()

View File

@ -50,7 +50,6 @@ class PreferenceController extends Zend_Controller_Action
Application_Model_Preference::SetAllow3rdPartyApi($values['thirdPartyApi']); Application_Model_Preference::SetAllow3rdPartyApi($values['thirdPartyApi']);
Application_Model_Preference::SetAllowedCorsUrls($values['allowedCorsUrls']); Application_Model_Preference::SetAllowedCorsUrls($values['allowedCorsUrls']);
Application_Model_Preference::SetDefaultLocale($values['locale']); Application_Model_Preference::SetDefaultLocale($values['locale']);
Application_Model_Preference::SetDefaultTimezone($values['timezone']);
Application_Model_Preference::SetWeekStartDay($values['weekStartDay']); Application_Model_Preference::SetWeekStartDay($values['weekStartDay']);
Application_Model_Preference::setRadioPageDisplayLoginButton($values['radioPageLoginButton']); Application_Model_Preference::setRadioPageDisplayLoginButton($values['radioPageLoginButton']);
Application_Model_Preference::SetFeaturePreviewMode($values['featurePreviewMode']); Application_Model_Preference::SetFeaturePreviewMode($values['featurePreviewMode']);

View File

@ -25,7 +25,6 @@ class SetupController extends Zend_Controller_Action
$currentUserId = $currentUser->getDbId(); $currentUserId = $currentUser->getDbId();
Application_Model_Preference::SetUserTimezone($formData['setup_timezone'], $currentUserId); Application_Model_Preference::SetUserTimezone($formData['setup_timezone'], $currentUserId);
Application_Model_Preference::SetDefaultTimezone($formData['setup_timezone']);
Application_Model_Preference::SetUserLocale($formData['setup_language'], $currentUserId); Application_Model_Preference::SetUserLocale($formData['setup_language'], $currentUserId);
Application_Model_Preference::SetDefaultLocale($formData['setup_language']); Application_Model_Preference::SetDefaultLocale($formData['setup_language']);

View File

@ -187,6 +187,7 @@ class Application_Form_GeneralPreferences extends Zend_Form_SubForm
// Form Element for setting the Timezone // Form Element for setting the Timezone
$timezone = new Zend_Form_Element_Select('timezone'); $timezone = new Zend_Form_Element_Select('timezone');
$timezone->setLabel(_('Station Timezone')); $timezone->setLabel(_('Station Timezone'));
$timezone->setAttrib('disabled', 'true');
$timezone->setMultiOptions(Application_Common_Timezone::getTimezones()); $timezone->setMultiOptions(Application_Common_Timezone::getTimezones());
$timezone->setValue(Application_Model_Preference::GetDefaultTimezone()); $timezone->setValue(Application_Model_Preference::GetDefaultTimezone());
$this->addElement($timezone); $this->addElement($timezone);

View File

@ -536,21 +536,10 @@ class Application_Model_Preference
return sprintf(_('Powered by %s'), SAAS_PRODUCT_BRANDING_NAME); return sprintf(_('Powered by %s'), SAAS_PRODUCT_BRANDING_NAME);
} }
// Sets station default timezone (from preferences)
public static function SetDefaultTimezone($timezone)
{
self::setValue('timezone', $timezone);
}
// Returns station default timezone (from preferences) // Returns station default timezone (from preferences)
public static function GetDefaultTimezone() public static function GetDefaultTimezone()
{ {
$stationTimezone = self::getValue('timezone'); return Config::get('general.timezone');
if (is_null($stationTimezone) || $stationTimezone == '') {
$stationTimezone = 'UTC';
}
return $stationTimezone;
} }
public static function SetUserTimezone($timezone = null) public static function SetUserTimezone($timezone = null)

View File

@ -5,6 +5,11 @@ from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Union
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, validator from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, validator
from typing_extensions import Annotated, Literal from typing_extensions import Annotated, Literal
try:
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
except ImportError:
from backports.zoneinfo import ZoneInfo, ZoneInfoNotFoundError # type: ignore
if TYPE_CHECKING: if TYPE_CHECKING:
from pydantic.typing import AnyClassMethod from pydantic.typing import AnyClassMethod
@ -38,9 +43,21 @@ class GeneralConfig(BaseModel):
public_url: AnyHttpUrl public_url: AnyHttpUrl
api_key: str api_key: str
timezone: str = "UTC"
# Validators # Validators
_public_url_no_trailing_slash = no_trailing_slash_validator("public_url") _public_url_no_trailing_slash = no_trailing_slash_validator("public_url")
@validator("timezone")
@classmethod
def _validate_timezone(cls, value: str) -> str:
try:
ZoneInfo(value)
except ZoneInfoNotFoundError as exception:
raise ValueError(f"invalid timezone '{value}'") from exception
return value
# StorageConfig # StorageConfig
######################################################################################## ########################################################################################

View File

@ -1,5 +1,6 @@
# Please do not edit this file, edit the setup.py file! # Please do not edit this file, edit the setup.py file!
# This file is auto-generated by tools/extract_requirements.py. # This file is auto-generated by tools/extract_requirements.py.
backports.zoneinfo>=0.2.1,<0.3;python_version<'3.9'
click~=8.0.4 click~=8.0.4
loguru==0.6.0 loguru==0.6.0
pydantic>=1.7.4,<1.11 pydantic>=1.7.4,<1.11

View File

@ -10,6 +10,7 @@ setup(
packages=find_packages(exclude=["*tests*", "*fixtures*"]), packages=find_packages(exclude=["*tests*", "*fixtures*"]),
package_data={"": ["py.typed"]}, package_data={"": ["py.typed"]},
install_requires=[ install_requires=[
"backports.zoneinfo>=0.2.1,<0.3;python_version<'3.9'",
"click~=8.0.4", "click~=8.0.4",
"loguru==0.6.0", "loguru==0.6.0",
"pydantic>=1.7.4,<1.11", "pydantic>=1.7.4,<1.11",
@ -17,6 +18,7 @@ setup(
], ],
extras_require={ extras_require={
"dev": [ "dev": [
"types-backports",
"types-pyyaml", "types-pyyaml",
], ],
}, },

View File

@ -6,10 +6,22 @@ from libretime_shared.config._models import (
AudioMP3, AudioMP3,
AudioOGG, AudioOGG,
AudioOpus, AudioOpus,
GeneralConfig,
StreamConfig, StreamConfig,
) )
def test_general_config_timezone():
defaults = {
"public_url": "http://localhost:8080",
"api_key": "api_key",
}
GeneralConfig(**defaults, timezone="UTC")
GeneralConfig(**defaults, timezone="Europe/Berlin")
with pytest.raises(ValidationError):
GeneralConfig(**defaults, timezone="Europe/Invalid")
@pytest.mark.parametrize( @pytest.mark.parametrize(
"audio", "audio",
[ [