We are using Django for Speedy Net and Speedy Match (currently Django 1.11.17, we can't upgrade to a newer version of Django because of one of our requirements, django-modeltranslation). I want to define some of our settings as classes. For example:
class UserSettings(object):
MIN_USERNAME_LENGTH = 6
MAX_USERNAME_LENGTH = 40
MIN_SLUG_LENGTH = 6
MAX_SLUG_LENGTH = 200
# Users can register from age 0 to 180, but can't be kept on the site after age 250.
MIN_AGE_ALLOWED_IN_MODEL = 0 # In years.
MAX_AGE_ALLOWED_IN_MODEL = 250 # In years.
MIN_AGE_ALLOWED_IN_FORMS = 0 # In years.
MAX_AGE_ALLOWED_IN_FORMS = 180 # In years.
MIN_PASSWORD_LENGTH = 8
MAX_PASSWORD_LENGTH = 120
PASSWORD_VALIDATORS = [
{
'NAME': 'speedy.core.accounts.validators.PasswordMinLengthValidator',
},
{
'NAME': 'speedy.core.accounts.validators.PasswordMaxLengthValidator',
},
]
(which is defined in https://github.com/speedy-net/speedy-net/blob/staging/speedy/net/settings/global_settings.py). And then in the models, I tried to use:
from django.conf import settings as django_settings
class User(ValidateUserPasswordMixin, PermissionsMixin, Entity, AbstractBaseUser):
settings = django_settings.UserSettings
(and then use attributes of settings
, such as settings.MIN_USERNAME_LENGTH
, in the class).
But it throws an exception
AttributeError: 'Settings' object has no attribute 'UserSettings'
(but it doesn't throw an exception if I use there a constant which is not a class).
This is the first problem. In the meantime, I defined instead:
from speedy.net.settings import global_settings as speedy_net_global_settings
class User(ValidateUserPasswordMixin, PermissionsMixin, Entity, AbstractBaseUser):
settings = speedy_net_global_settings.UserSettings
The second problem, is how do I override such settings in tests? For example, I use the following code:
from speedy.core.settings import tests as tests_settings
@override_settings(MAX_NUMBER_OF_FRIENDS_ALLOWED=tests_settings.OVERRIDE_MAX_NUMBER_OF_FRIENDS_ALLOWED)
in https://github.com/speedy-net/speedy-net/blob/staging/speedy/core/friends/tests/test_views.py. But if MAX_NUMBER_OF_FRIENDS_ALLOWED
would be defined in the class UserSettings
, how do I override it?
Django doesn't expect you to deviate much from its low-level design choices and it's usually a struggle to work around things that Django doesn't explicitly allow you to customize.
Django's settings object explicitly skips over any objects in your settings module with non-uppercase names. If you rename your class to USER_SETTINGS
, it will work. If you really want to keep your object's original name a horrible solution would be to trick Django:
class UserSettings:
...
class AlwaysUppercaseStr(str):
def isupper(self):
return True
globals()[AlwaysUppercaseStr('UserSettings')] = globals().pop('UserSettings')
I have no idea if this is portable across Python implementations but it works with CPython's dir()
.
override_settings
has no support for what you're trying to do so you will probably need to rewrite that class to allow the global settings
object to be configurable.
Thanks to @Blender for the tip:
Django's settings object explicitly skips over any objects in your
settings module with non-uppercase names. If you rename your class to
USER_SETTINGS, it will work.
I was not aware that all the settings have to be uppercase. So I renamed class UserSettings
to class USER_SETTINGS
(although PyCharm doesn't like it), but I checked and it's also possible to add this code at the end of the file:
USER_SETTINGS = UserSettings
Without renaming the class.
As for my second question - how do I override such settings in tests? I added a file called utils.py
:
def get_django_settings_class_with_override_settings(django_settings_class, **override_settings):
class django_settings_class_with_override_settings(django_settings_class):
pass
for setting, value in override_settings.items():
setattr(django_settings_class_with_override_settings, setting, value)
return django_settings_class_with_override_settings
(You can see it on https://github.com/speedy-net/speedy-net/blob/staging/speedy/core/base/test/utils.py)
And then in the tests:
from django.conf import settings as django_settings
from django.test import override_settings
from speedy.core.settings import tests as tests_settings
from speedy.core.base.test.utils import get_django_settings_class_with_override_settings
@override_settings(USER_SETTINGS=get_django_settings_class_with_override_settings(django_settings_class=django_settings.USER_SETTINGS, MAX_NUMBER_OF_FRIENDS_ALLOWED=tests_settings.OVERRIDE_USER_SETTINGS.MAX_NUMBER_OF_FRIENDS_ALLOWED))
def test_user_can_send_friend_request_if_not_maximum(self):
self.assertEqual(first=django_settings.USER_SETTINGS.MAX_NUMBER_OF_FRIENDS_ALLOWED, second=4)
I checked and I have to define another class (in this case, class django_settings_class_with_override_settings
because if I change the class django_settings_class
directly it also affects other tests which didn't use @override_settings
.