diff --git a/openwisp_radius/base/fields.py b/openwisp_radius/base/fields.py deleted file mode 100644 index da08ab3f..00000000 --- a/openwisp_radius/base/fields.py +++ /dev/null @@ -1,133 +0,0 @@ -from django import forms -from django.db.models.fields import BooleanField, CharField, TextField, URLField -from django.utils.translation import gettext_lazy as _ - - -class FallbackMixin(object): - def __init__(self, *args, **kwargs): - self.fallback = kwargs.pop('fallback', None) - super().__init__(*args, **kwargs) - - def deconstruct(self): - name, path, args, kwargs = super().deconstruct() - kwargs['fallback'] = self.fallback - return (name, path, args, kwargs) - - -class FallbackFromDbValueMixin: - """ - Returns the fallback value when the value of the field - is falsy (None or ''). - - It does not set the field's value to "None" when the value - is equal to the fallback value. This allows overriding of - the value when a user knows that the default will get changed. - """ - - def from_db_value(self, value, expression, connection): - if value is None: - return self.fallback - return value - - -class FalsyValueNoneMixin: - """ - If the field contains an empty string, then - stores "None" in the database if the field is - nullable. - """ - - # TDjango convention is to use the empty string, not NULL - # for representing "no data" in the database. - # https://docs.djangoproject.com/en/dev/ref/models/fields/#null - # We need to use NULL for fallback field here to keep - # the fallback logic simple. Hence, we allow only "None" (NULL) - # as empty value here. - empty_values = [None] - - def clean(self, value, model_instance): - if not value and self.null is True: - return None - return super().clean(value, model_instance) - - -class FallbackBooleanChoiceField(FallbackMixin, BooleanField): - def formfield(self, **kwargs): - default_value = _('Enabled') if self.fallback else _('Disabled') - kwargs.update( - { - "form_class": forms.NullBooleanField, - 'widget': forms.Select( - choices=[ - ( - '', - _('Default') + f' ({default_value})', - ), - (True, _('Enabled')), - (False, _('Disabled')), - ], - attrs={'data-default-value': str(self.fallback)}, - ), - } - ) - return super().formfield(**kwargs) - - -class FallbackCharChoiceField(FallbackMixin, CharField): - def get_choices(self, **kwargs): - for choice, value in self.choices: - if choice == self.fallback: - default = value - break - kwargs.update({'blank_choice': [('', _('Default') + f' ({default})')]}) - return super().get_choices(**kwargs) - - def formfield(self, **kwargs): - kwargs.update( - { - "choices_form_class": forms.TypedChoiceField, - } - ) - return super().formfield(**kwargs) - - -class FallbackCharField( - FallbackMixin, FalsyValueNoneMixin, FallbackFromDbValueMixin, CharField -): - """ - Populates the form with the fallback value - if the value is set to null in the database. - """ - - pass - - -class FallbackURLField( - FallbackMixin, FalsyValueNoneMixin, FallbackFromDbValueMixin, URLField -): - """ - Populates the form with the fallback value - if the value is set to null in the database. - """ - - pass - - -class FallbackTextField( - FallbackMixin, FalsyValueNoneMixin, FallbackFromDbValueMixin, TextField -): - """ - Populates the form with the fallback value - if the value is set to null in the database. - """ - - def formfield(self, **kwargs): - kwargs.update({'form_class': FallbackTextFormField}) - return super().formfield(**kwargs) - - -class FallbackTextFormField(forms.CharField): - def widget_attrs(self, widget): - attrs = super().widget_attrs(widget) - attrs.update({'rows': 2, 'cols': 34, 'style': 'width:auto'}) - return attrs diff --git a/openwisp_radius/base/models.py b/openwisp_radius/base/models.py index 57ded646..50534f8e 100644 --- a/openwisp_radius/base/models.py +++ b/openwisp_radius/base/models.py @@ -28,6 +28,12 @@ from openwisp_radius.registration import REGISTRATION_METHOD_CHOICES from openwisp_users.mixins import OrgMixin from openwisp_utils.base import KeyField, TimeStampedEditableModel, UUIDModel +from openwisp_utils.fields import ( + FallbackBooleanChoiceField, + FallbackCharChoiceField, + FallbackCharField, + FallbackTextField, +) from .. import exceptions from .. import settings as app_settings @@ -47,12 +53,6 @@ prefix_generate_users, validate_csvfile, ) -from .fields import ( - FallbackBooleanChoiceField, - FallbackCharChoiceField, - FallbackCharField, - FallbackTextField, -) from .validators import ipv6_network_validator, password_reset_url_validator logger = logging.getLogger(__name__) diff --git a/openwisp_radius/migrations/0031_added_fallback_model_fields.py b/openwisp_radius/migrations/0031_added_fallback_model_fields.py index 59171faf..9ac796ae 100644 --- a/openwisp_radius/migrations/0031_added_fallback_model_fields.py +++ b/openwisp_radius/migrations/0031_added_fallback_model_fields.py @@ -2,7 +2,7 @@ from django.db import migrations -import openwisp_radius.base.fields +import openwisp_utils.fields from .. import settings as app_settings @@ -17,7 +17,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='allowed_mobile_prefixes', - field=openwisp_radius.base.fields.FallbackTextField( + field=openwisp_utils.fields.FallbackTextField( blank=True, default=','.join(app_settings.ALLOWED_MOBILE_PREFIXES), fallback=','.join(app_settings.ALLOWED_MOBILE_PREFIXES), @@ -31,7 +31,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='birth_date', - field=openwisp_radius.base.fields.FallbackCharChoiceField( + field=openwisp_utils.fields.FallbackCharChoiceField( blank=True, choices=[ ('disabled', 'Disabled'), @@ -53,7 +53,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='first_name', - field=openwisp_radius.base.fields.FallbackCharChoiceField( + field=openwisp_utils.fields.FallbackCharChoiceField( blank=True, choices=[ ('disabled', 'Disabled'), @@ -75,7 +75,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='freeradius_allowed_hosts', - field=openwisp_radius.base.fields.FallbackTextField( + field=openwisp_utils.fields.FallbackTextField( blank=True, default=','.join(app_settings.FREERADIUS_ALLOWED_HOSTS), fallback=','.join(app_settings.FREERADIUS_ALLOWED_HOSTS), @@ -89,7 +89,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='last_name', - field=openwisp_radius.base.fields.FallbackCharChoiceField( + field=openwisp_utils.fields.FallbackCharChoiceField( blank=True, choices=[ ('disabled', 'Disabled'), @@ -111,7 +111,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='location', - field=openwisp_radius.base.fields.FallbackCharChoiceField( + field=openwisp_utils.fields.FallbackCharChoiceField( blank=True, choices=[ ('disabled', 'Disabled'), @@ -133,7 +133,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='needs_identity_verification', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -147,7 +147,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='password_reset_url', - field=openwisp_radius.base.fields.FallbackURLField( + field=openwisp_utils.fields.FallbackURLField( blank=True, default=app_settings.DEFAULT_PASSWORD_RESET_URL, fallback=app_settings.DEFAULT_PASSWORD_RESET_URL, @@ -159,7 +159,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='registration_enabled', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -172,7 +172,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='saml_registration_enabled', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -186,7 +186,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='sms_verification', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -201,7 +201,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='social_registration_enabled', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( diff --git a/openwisp_radius/migrations/0032_organizationradiussettings_sms_message.py b/openwisp_radius/migrations/0032_organizationradiussettings_sms_message.py index 88bdc737..868c68fd 100644 --- a/openwisp_radius/migrations/0032_organizationradiussettings_sms_message.py +++ b/openwisp_radius/migrations/0032_organizationradiussettings_sms_message.py @@ -2,7 +2,7 @@ from django.db import migrations -import openwisp_radius.base.fields +import openwisp_utils.fields from openwisp_radius import settings as app_settings @@ -16,7 +16,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='organizationradiussettings', name='sms_message', - field=openwisp_radius.base.fields.FallbackTextField( + field=openwisp_utils.fields.FallbackTextField( blank=True, fallback=app_settings.SMS_MESSAGE_TEMPLATE, help_text=( diff --git a/openwisp_radius/migrations/0033_alter_organizationradiussettings_password_reset_url.py b/openwisp_radius/migrations/0033_alter_organizationradiussettings_password_reset_url.py index d2a6a752..b9532bc1 100644 --- a/openwisp_radius/migrations/0033_alter_organizationradiussettings_password_reset_url.py +++ b/openwisp_radius/migrations/0033_alter_organizationradiussettings_password_reset_url.py @@ -2,8 +2,8 @@ from django.db import migrations -import openwisp_radius.base.fields import openwisp_radius.base.validators +import openwisp_utils.fields from openwisp_radius import settings as app_settings @@ -17,7 +17,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='password_reset_url', - field=openwisp_radius.base.fields.FallbackCharField( + field=openwisp_utils.fields.FallbackCharField( blank=True, default=app_settings.DEFAULT_PASSWORD_RESET_URL, fallback=app_settings.DEFAULT_PASSWORD_RESET_URL, diff --git a/openwisp_radius/migrations/0034_organizationradiussettings_coa_enabled.py b/openwisp_radius/migrations/0034_organizationradiussettings_coa_enabled.py index 1eca3748..2af8dcfe 100644 --- a/openwisp_radius/migrations/0034_organizationradiussettings_coa_enabled.py +++ b/openwisp_radius/migrations/0034_organizationradiussettings_coa_enabled.py @@ -2,7 +2,7 @@ from django.db import migrations -import openwisp_radius.base.fields +import openwisp_utils.fields from openwisp_radius import settings as app_settings @@ -16,7 +16,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='organizationradiussettings', name='coa_enabled', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, fallback=app_settings.COA_ENABLED, diff --git a/openwisp_radius/tests/test_models.py b/openwisp_radius/tests/test_models.py index dd4a6ae4..a1757302 100644 --- a/openwisp_radius/tests/test_models.py +++ b/openwisp_radius/tests/test_models.py @@ -7,7 +7,6 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError -from django.db import connection from django.db.models import ProtectedError from django.urls import reverse from django.utils import timezone @@ -883,37 +882,6 @@ def test_delete_csv_file(self): batch.delete() -class TestOrganizationRadiusSettings(BaseTestCase): - def test_fallback_field_falsy_values(self): - org = self._get_org() - rad_settings = org.radius_settings - - def _verify_none_database_value(field_name): - setattr(rad_settings, field_name, '') - rad_settings.full_clean() - rad_settings.save() - with connection.cursor() as cursor: - cursor.execute( - f'SELECT {field_name} FROM' - f' {rad_settings._meta.app_label}_{rad_settings._meta.model_name}' - f' WHERE id = \'{rad_settings.id.hex}\';', - ) - row = cursor.fetchone() - self.assertEqual(row[0], None) - - with self.subTest('Test "sms_message" field'): - _verify_none_database_value('sms_message') - - with self.subTest('Test "freeradius_allowed_hosts" field'): - _verify_none_database_value('freeradius_allowed_hosts') - - with self.subTest('Test "allowed_mobile_prefixes" field'): - _verify_none_database_value('allowed_mobile_prefixes') - - with self.subTest('Test "password_reset_url" field'): - _verify_none_database_value('password_reset_url') - - class TestChangeOfAuthorization(BaseTransactionTestCase): def _change_radius_user_group(self, user, organization): rad_user_group = user.radiususergroup_set.first() diff --git a/tests/openwisp2/sample_radius/migrations/0028_alter_organizationradiussettings_allowed_mobile_prefixes_and_more.py b/tests/openwisp2/sample_radius/migrations/0028_alter_organizationradiussettings_allowed_mobile_prefixes_and_more.py index cbea6e62..d33a8608 100644 --- a/tests/openwisp2/sample_radius/migrations/0028_alter_organizationradiussettings_allowed_mobile_prefixes_and_more.py +++ b/tests/openwisp2/sample_radius/migrations/0028_alter_organizationradiussettings_allowed_mobile_prefixes_and_more.py @@ -2,7 +2,8 @@ from django.db import migrations -import openwisp_radius.base.fields +import openwisp_radius +import openwisp_utils.fields from openwisp_radius import settings as app_settings @@ -16,7 +17,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='allowed_mobile_prefixes', - field=openwisp_radius.base.fields.FallbackTextField( + field=openwisp_utils.fields.FallbackTextField( blank=True, default=','.join(app_settings.ALLOWED_MOBILE_PREFIXES), fallback=','.join(app_settings.ALLOWED_MOBILE_PREFIXES), @@ -30,7 +31,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='birth_date', - field=openwisp_radius.base.fields.FallbackCharChoiceField( + field=openwisp_utils.fields.FallbackCharChoiceField( blank=True, choices=[ ('disabled', 'Disabled'), @@ -52,7 +53,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='first_name', - field=openwisp_radius.base.fields.FallbackCharChoiceField( + field=openwisp_utils.fields.FallbackCharChoiceField( blank=True, choices=[ ('disabled', 'Disabled'), @@ -74,7 +75,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='freeradius_allowed_hosts', - field=openwisp_radius.base.fields.FallbackTextField( + field=openwisp_utils.fields.FallbackTextField( blank=True, default=','.join(app_settings.FREERADIUS_ALLOWED_HOSTS), fallback=','.join(app_settings.FREERADIUS_ALLOWED_HOSTS), @@ -88,7 +89,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='last_name', - field=openwisp_radius.base.fields.FallbackCharChoiceField( + field=openwisp_utils.fields.FallbackCharChoiceField( blank=True, choices=[ ('disabled', 'Disabled'), @@ -110,7 +111,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='location', - field=openwisp_radius.base.fields.FallbackCharChoiceField( + field=openwisp_utils.fields.FallbackCharChoiceField( blank=True, choices=[ ('disabled', 'Disabled'), @@ -132,7 +133,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='needs_identity_verification', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -146,7 +147,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='password_reset_url', - field=openwisp_radius.base.fields.FallbackCharField( + field=openwisp_utils.fields.FallbackCharField( blank=True, default=app_settings.DEFAULT_PASSWORD_RESET_URL, fallback=app_settings.DEFAULT_PASSWORD_RESET_URL, @@ -162,7 +163,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='registration_enabled', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -175,7 +176,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='saml_registration_enabled', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -189,7 +190,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='sms_verification', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -204,7 +205,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organizationradiussettings', name='social_registration_enabled', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, help_text=( @@ -218,7 +219,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='organizationradiussettings', name='sms_message', - field=openwisp_radius.base.fields.FallbackTextField( + field=openwisp_utils.fields.FallbackTextField( blank=True, fallback=app_settings.SMS_MESSAGE_TEMPLATE, help_text=( @@ -233,7 +234,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='organizationradiussettings', name='coa_enabled', - field=openwisp_radius.base.fields.FallbackBooleanChoiceField( + field=openwisp_utils.fields.FallbackBooleanChoiceField( blank=True, default=None, fallback=app_settings.COA_ENABLED,