From c21d2b46d9f738e324a380709c159b70a2c4d20c Mon Sep 17 00:00:00 2001 From: CodingWithSaksham Date: Thu, 18 Jun 2026 13:39:06 +0530 Subject: [PATCH 1/3] [change] Squashed migrations 0001-0042 for faster fresh installs #705 Squashed only the migrations released up to 1.2.x (0001-0042) into `0001_squashed_0042_initial`, keeping 0043-0048 (unreleased 1.3.0 work) standalone. Flagged the data-migration helpers used solely by the squashed range in `migrations/__init__.py` for removal in a future release. Closes #705 --- .../migrations/0001_squashed_0042_initial.py | 2257 +++++++++++++++++ openwisp_radius/migrations/__init__.py | 25 + 2 files changed, 2282 insertions(+) create mode 100644 openwisp_radius/migrations/0001_squashed_0042_initial.py diff --git a/openwisp_radius/migrations/0001_squashed_0042_initial.py b/openwisp_radius/migrations/0001_squashed_0042_initial.py new file mode 100644 index 00000000..651d0538 --- /dev/null +++ b/openwisp_radius/migrations/0001_squashed_0042_initial.py @@ -0,0 +1,2257 @@ +# Generated by Django 5.2.15 on 2026-06-18 05:41 + +import re +import uuid +from urllib.parse import urljoin + +import django.core.validators +import django.db.migrations.operations.special +import django.db.models.deletion +import django.utils.timezone +import model_utils.fields +import phonenumber_field.modelfields +import private_storage.fields +import private_storage.storage.files +import swapper +from django.conf import settings +from django.db import migrations, models + +import openwisp_radius.base.models +import openwisp_radius.base.validators +import openwisp_radius.migrations +import openwisp_radius.registration +import openwisp_radius.utils +import openwisp_users.mixins +import openwisp_utils.fields +import openwisp_utils.utils +from openwisp_radius import settings as app_settings +from openwisp_utils.fields import FallbackMixin + + +# Copied verbatim from 0038_clean_fallbackfields and 0042_set_existing_batches_completed +# because squashmigrations cannot import functions from modules whose names start +# with a digit. Keep these in sync if the originals ever change. +def clean_fallback_fields(apps, schema_editor): + OrganizationRadiusSettings = openwisp_radius.migrations.get_swapped_model( + apps, "openwisp_radius", "organizationradiussettings" + ) + fallback_fields = [] + for field in OrganizationRadiusSettings._meta.get_fields(): + if isinstance(field, FallbackMixin): + fallback_fields.append(field) + updated_settings = [] + for radius_settings in OrganizationRadiusSettings.objects.iterator(): + changed = False + for field in fallback_fields: + if getattr(radius_settings, field.name) == field.fallback: + setattr(radius_settings, field.name, None) + changed = True + if changed: + updated_settings.append(radius_settings) + if len(updated_settings) > 100: + OrganizationRadiusSettings.objects.bulk_update( + updated_settings, fields=[f.name for f in fallback_fields] + ) + updated_settings = [] + if updated_settings: + OrganizationRadiusSettings.objects.bulk_update( + updated_settings, fields=[f.name for f in fallback_fields] + ) + + +def set_existing_batches_completed(apps, schema_editor): + RadiusBatch = apps.get_model("openwisp_radius", "RadiusBatch") + RadiusBatch.objects.all().update(status="completed") + + +class Migration(migrations.Migration): + + replaces = [ + ("openwisp_radius", "0001_initial_freeradius"), + ("openwisp_radius", "0002_initial_openwisp_radius"), + ("openwisp_radius", "0003_default_radius_groups"), + ("openwisp_radius", "0004_default_permissions"), + ("openwisp_radius", "0005_radiustoken"), + ("openwisp_radius", "0006_add_radactt_fields"), + ("openwisp_radius", "0007_sms_verification"), + ("openwisp_radius", "0008_sms_sender"), + ("openwisp_radius", "0009_radbatch_user_credentials_field"), + ("openwisp_radius", "0010_csv_private_storage"), + ("openwisp_radius", "0011_add_null_uuid_field"), + ("openwisp_radius", "0012_populate_uuid_field"), + ("openwisp_radius", "0013_remove_null_uuid_field"), + ("openwisp_radius", "0014_radiustoken_freeradius_auth"), + ("openwisp_radius", "0015_freeradius_allowed_hosts"), + ("openwisp_radius", "0016_allowed_mobile_prefixes"), + ("openwisp_radius", "0017_phonetoken_phone_number"), + ("openwisp_radius", "0018_populate_phonetoken_phone_number"), + ("openwisp_radius", "0019_made_phonetoken_phone_number_required"), + ("openwisp_radius", "0020_added_optional_registration_fields"), + ("openwisp_radius", "0021_radius_user_group_unique_together"), + ("openwisp_radius", "0022_organizationradiussettings_registration_enabled"), + ("openwisp_radius", "0023_registered_user"), + ("openwisp_radius", "0024_registereduser_modified"), + ("openwisp_radius", "0025_sms_verification"), + ("openwisp_radius", "0026_login_status_url_org_settings"), + ("openwisp_radius", "0027_password_reset_url_org_settings"), + ( + "openwisp_radius", + "0028_organizationradiussettings_saml_social_registration_enabled", + ), + ("openwisp_radius", "0029_remove_check_customizations"), + ("openwisp_radius", "0030_remove_radiuscheck_notes"), + ("openwisp_radius", "0031_added_fallback_model_fields"), + ("openwisp_radius", "0032_organizationradiussettings_sms_message"), + ("openwisp_radius", "0033_alter_organizationradiussettings_password_reset_url"), + ("openwisp_radius", "0034_organizationradiussettings_coa_enabled"), + ("openwisp_radius", "0035_organizationradiussettings_sms_cooldown"), + ("openwisp_radius", "0036_organizationradiussettings_mac_addr_roaming_enabled"), + ( + "openwisp_radius", + "0037_alter_organizationradiussettings_allowed_mobile_prefixes_and_more", + ), + ("openwisp_radius", "0038_clean_fallbackfields"), + ("openwisp_radius", "0039_alter_radiusaccounting_called_station_id_and_more"), + ("openwisp_radius", "0040_rename_phonetoken_index"), + ("openwisp_radius", "0041_radiusbatch_status"), + ("openwisp_radius", "0042_set_existing_batches_completed"), + ] + + initial = True + + dependencies = [ + swapper.dependency( + *swapper.split(settings.AUTH_USER_MODEL), + version="0004_default_groups", + ), + swapper.dependency("openwisp_users", "Organization"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="RadiusGroup", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "name", + models.CharField( + db_index=True, + max_length=255, + unique=True, + verbose_name="group name", + ), + ), + ( + "description", + models.CharField( + blank=True, max_length=64, null=True, verbose_name="description" + ), + ), + ( + "default", + models.BooleanField( + default=False, + help_text=( + "The default group is automatically assigned to new users;" + " changing the default group has only effect on new users" + " (existing users will keep being members of their current" + " group)" + ), + verbose_name="is default?", + ), + ), + ( + "organization", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + ], + options={ + "verbose_name": "group", + "verbose_name_plural": "groups", + "abstract": False, + "swappable": "OPENWISP_RADIUS_RADIUSGROUP_MODEL", + }, + bases=(openwisp_users.mixins.ValidateOrgMixin, models.Model), + ), + migrations.CreateModel( + name="RadiusGroupCheck", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "groupname", + models.CharField( + blank=True, + db_index=True, + max_length=64, + verbose_name="group name", + ), + ), + ( + "attribute", + models.CharField(max_length=64, verbose_name="attribute"), + ), + ( + "op", + models.CharField( + choices=[ + ("=", "="), + (":=", ":="), + ("==", "=="), + ("+=", "+="), + ("!=", "!="), + (">", ">"), + (">=", ">="), + ("<", "<"), + ("<=", "<="), + ("=~", "=~"), + ("!~", "!~"), + ("=*", "=*"), + ("!*", "!*"), + ], + default=":=", + max_length=2, + verbose_name="operator", + ), + ), + ("value", models.CharField(max_length=253, verbose_name="value")), + ( + "group", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.OPENWISP_RADIUS_RADIUSGROUP_MODEL, + ), + ), + ], + options={ + "swappable": "OPENWISP_RADIUS_RADIUSGROUPCHECK_MODEL", + "db_table": "radgroupcheck", + "verbose_name_plural": "group checks", + "abstract": False, + "verbose_name": "group check", + }, + bases=(openwisp_radius.base.models.AutoGroupnameMixin, models.Model), + ), + migrations.CreateModel( + name="RadiusGroupReply", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "groupname", + models.CharField( + blank=True, + db_index=True, + max_length=64, + verbose_name="group name", + ), + ), + ( + "attribute", + models.CharField(max_length=64, verbose_name="attribute"), + ), + ( + "op", + models.CharField( + choices=[("=", "="), (":=", ":="), ("+=", "+=")], + default="=", + max_length=2, + verbose_name="operator", + ), + ), + ("value", models.CharField(max_length=253, verbose_name="value")), + ( + "group", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.OPENWISP_RADIUS_RADIUSGROUP_MODEL, + ), + ), + ], + options={ + "swappable": "OPENWISP_RADIUS_RADIUSGROUPREPLY_MODEL", + "db_table": "radgroupreply", + "verbose_name_plural": "group replies", + "abstract": False, + "verbose_name": "group reply", + }, + bases=(openwisp_radius.base.models.AutoGroupnameMixin, models.Model), + ), + migrations.CreateModel( + name="RadiusUserGroup", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "username", + models.CharField( + blank=True, + db_index=True, + max_length=64, + verbose_name="username", + ), + ), + ( + "groupname", + models.CharField( + blank=True, max_length=64, verbose_name="group name" + ), + ), + ("priority", models.IntegerField(default=1, verbose_name="priority")), + ( + "group", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.OPENWISP_RADIUS_RADIUSGROUP_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "swappable": "OPENWISP_RADIUS_RADIUSUSERGROUP_MODEL", + "db_table": "radusergroup", + "verbose_name_plural": "user groups", + "abstract": False, + "verbose_name": "user group", + }, + bases=( + openwisp_radius.base.models.AutoGroupnameMixin, + openwisp_radius.base.models.AutoUsernameMixin, + models.Model, + ), + ), + migrations.CreateModel( + name="RadiusBatch", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "strategy", + models.CharField( + choices=[ + ("prefix", "Generate from prefix"), + ("csv", "Import from CSV"), + ], + db_index=True, + help_text="Import users from a CSV or generate using a prefix", + max_length=16, + verbose_name="strategy", + ), + ), + ( + "csvfile", + models.FileField( + blank=True, + help_text=( + "The csv file containing the user details to be uploaded" + ), + null=True, + upload_to="", + verbose_name="CSV", + ), + ), + ( + "prefix", + models.CharField( + blank=True, + help_text=( + "Usernames generated will be of the format [prefix][number]" + ), + max_length=20, + null=True, + verbose_name="prefix", + ), + ), + ( + "pdf", + models.FileField( + blank=True, + help_text=( + "The pdf file containing list of usernames and passwords" + ), + null=True, + upload_to="", + verbose_name="PDF", + ), + ), + ( + "expiration_date", + models.DateField( + blank=True, + help_text="If left blank users will never expire", + null=True, + verbose_name="expiration date", + ), + ), + ( + "name", + models.CharField( + db_index=True, + help_text="A unique batch name", + max_length=128, + verbose_name="name", + ), + ), + ( + "organization", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + ( + "users", + models.ManyToManyField( + blank=True, + help_text="List of users uploaded in this batch", + related_name="radius_batch", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "batch user creation", + "verbose_name_plural": "batch user creation operations", + "db_table": "radbatch", + "abstract": False, + "swappable": "OPENWISP_RADIUS_RADIUSBATCH_MODEL", + "unique_together": {("name", "organization")}, + }, + bases=(openwisp_users.mixins.ValidateOrgMixin, models.Model), + ), + migrations.CreateModel( + name="OrganizationRadiusSettings", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "token", + openwisp_utils.fields.KeyField( + default=openwisp_utils.utils.get_random_key, + help_text=None, + max_length=32, + validators=[ + django.core.validators.RegexValidator( + re.compile("^[^\\s/\\.]+$"), + code="invalid", + message=( + "This value must not contain spaces, dots or" + " slashes." + ), + ) + ], + ), + ), + ( + "organization", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="radius_settings", + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + ], + options={ + "abstract": False, + "verbose_name": "Organization radius settings", + "verbose_name_plural": "Organization radius settings", + }, + bases=(openwisp_users.mixins.ValidateOrgMixin, models.Model), + ), + migrations.CreateModel( + name="Nas", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "name", + models.CharField( + db_column="nasname", + db_index=True, + help_text="NAS Name (or IP address)", + max_length=128, + verbose_name="name", + ), + ), + ( + "short_name", + models.CharField( + db_column="shortname", max_length=32, verbose_name="short name" + ), + ), + ( + "type", + models.CharField( + choices=[ + ("cisco", "Cisco Router"), + ("Async", "Async"), + ("Sync", "Sync"), + ("ISDN Sync", "ISDN Sync"), + ("ISDN Async V.120", "ISDN Async V.120"), + ("ISDN Async V.110", "ISDN Async V.110"), + ("Virtual", "Virtual"), + ("PIAFS", "PIAFS"), + ("HDLC Clear", "HDLC Clear"), + ("Channel", "Channel"), + ("X.25", "X.25"), + ("X.75", "X.75"), + ("G.3 Fax", "G.3 Fax"), + ("SDSL", "SDSL - Symmetric DSL"), + ("ADSL-CAP", "ADSL-CAP"), + ("ADSL-DMT", "ADSL-DMT"), + ("IDSL", "IDSL"), + ("Ethernet", "Ethernet"), + ("xDSL", "xDSL"), + ("Cable", "Cable"), + ("Wireless - Other", "Wireless - Other"), + ("IEEE 802.11", "Wireless - IEEE 802.11"), + ("Token-Ring", "Token-Ring"), + ("FDDI", "FDDI"), + ("Wireless - CDMA2000", "Wireless - CDMA2000"), + ("Wireless - UMTS", "Wireless - UMTS"), + ("Wireless - 1X-EV", "Wireless - 1X-EV"), + ("IAPP", "IAPP"), + ("FTTP", "FTTP"), + ("IEEE 802.16", "Wireless - IEEE 802.16"), + ("IEEE 802.20", "Wireless - IEEE 802.20"), + ("IEEE 802.22", "Wireless - IEEE 802.22"), + ("PPPoA", "PPPoA - PPP over ATM"), + ("PPPoEoA", "PPPoEoA - PPP over Ethernet over ATM"), + ("PPPoEoE", "PPPoEoE - PPP over Ethernet over Ethernet"), + ("PPPoEoVLAN", "PPPoEoVLAN - PPP over Ethernet over VLAN"), + ( + "PPPoEoQinQ", + "PPPoEoQinQ - PPP over Ethernet over IEEE 802.1QinQ", + ), + ("xPON", "xPON - Passive Optical Network"), + ("Wireless - XGP", "Wireless - XGP"), + ("WiMAX", " WiMAX Pre-Release 8 IWK Function"), + ( + "WIMAX-WIFI-IWK", + "WIMAX-WIFI-IWK: WiMAX WIFI Interworking", + ), + ( + "WIMAX-SFF", + ( + "WIMAX-SFF: Signaling Forwarding Function for" + " LTE/3GPP2" + ), + ), + ( + "WIMAX-HA-LMA", + "WIMAX-HA-LMA: WiMAX HA and or LMA function", + ), + ("WIMAX-DHCP", "WIMAX-DHCP: WIMAX DCHP service"), + ("WIMAX-LBS", "WIMAX-LBS: WiMAX location based service"), + ("WIMAX-WVS", "WIMAX-WVS: WiMAX voice service"), + ("Other", "Other"), + ], + default="other", + max_length=30, + verbose_name="type", + ), + ), + ( + "secret", + models.CharField( + help_text="Shared Secret", max_length=60, verbose_name="secret" + ), + ), + ( + "ports", + models.PositiveIntegerField( + blank=True, null=True, verbose_name="ports" + ), + ), + ( + "community", + models.CharField( + blank=True, max_length=50, null=True, verbose_name="community" + ), + ), + ( + "description", + models.CharField( + blank=True, + max_length=200, + null=True, + verbose_name="description", + ), + ), + ( + "server", + models.CharField( + blank=True, max_length=64, null=True, verbose_name="server" + ), + ), + ( + "organization", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + ], + options={ + "swappable": "OPENWISP_RADIUS_NAS_MODEL", + "db_table": "nas", + "verbose_name_plural": "NAS", + "abstract": False, + "verbose_name": "NAS", + }, + bases=(openwisp_users.mixins.ValidateOrgMixin, models.Model), + ), + migrations.CreateModel( + name="RadiusAccounting", + fields=[ + ( + "id", + models.BigAutoField( + db_column="radacctid", primary_key=True, serialize=False + ), + ), + ( + "session_id", + models.CharField( + db_column="acctsessionid", + db_index=True, + max_length=64, + verbose_name="session ID", + ), + ), + ( + "unique_id", + models.CharField( + db_column="acctuniqueid", + max_length=32, + unique=True, + verbose_name="accounting unique ID", + ), + ), + ( + "username", + models.CharField( + blank=True, + db_index=True, + max_length=64, + null=True, + verbose_name="username", + ), + ), + ( + "groupname", + models.CharField( + blank=True, max_length=64, null=True, verbose_name="group name" + ), + ), + ( + "realm", + models.CharField( + blank=True, max_length=64, null=True, verbose_name="realm" + ), + ), + ( + "nas_ip_address", + models.GenericIPAddressField( + db_column="nasipaddress", + db_index=True, + verbose_name="NAS IP address", + ), + ), + ( + "nas_port_id", + models.CharField( + blank=True, + db_column="nasportid", + max_length=15, + null=True, + verbose_name="NAS port ID", + ), + ), + ( + "nas_port_type", + models.CharField( + blank=True, + db_column="nasporttype", + max_length=32, + null=True, + verbose_name="NAS port type", + ), + ), + ( + "start_time", + models.DateTimeField( + blank=True, + db_column="acctstarttime", + db_index=True, + null=True, + verbose_name="start time", + ), + ), + ( + "stop_time", + models.DateTimeField( + blank=True, + db_column="acctstoptime", + db_index=True, + null=True, + verbose_name="stop time", + ), + ), + ( + "session_time", + models.PositiveIntegerField( + blank=True, + db_column="acctsessiontime", + null=True, + verbose_name="session time", + ), + ), + ( + "authentication", + models.CharField( + blank=True, + db_column="acctauthentic", + max_length=32, + null=True, + verbose_name="authentication", + ), + ), + ( + "connection_info_start", + models.CharField( + blank=True, + db_column="connectinfo_start", + max_length=50, + null=True, + verbose_name="connection info start", + ), + ), + ( + "connection_info_stop", + models.CharField( + blank=True, + db_column="connectinfo_stop", + max_length=50, + null=True, + verbose_name="connection info stop", + ), + ), + ( + "input_octets", + models.BigIntegerField( + blank=True, + db_column="acctinputoctets", + null=True, + verbose_name="input octets", + ), + ), + ( + "output_octets", + models.BigIntegerField( + blank=True, + db_column="acctoutputoctets", + null=True, + verbose_name="output octets", + ), + ), + ( + "calling_station_id", + models.CharField( + blank=True, + db_column="callingstationid", + db_index=True, + max_length=50, + null=True, + verbose_name="calling station ID", + ), + ), + ( + "called_station_id", + models.CharField( + blank=True, + db_column="calledstationid", + db_index=True, + max_length=50, + null=True, + verbose_name="called station ID", + ), + ), + ( + "terminate_cause", + models.CharField( + blank=True, + db_column="acctterminatecause", + max_length=32, + null=True, + verbose_name="termination cause", + ), + ), + ( + "service_type", + models.CharField( + blank=True, + db_column="servicetype", + max_length=32, + null=True, + verbose_name="service type", + ), + ), + ( + "framed_protocol", + models.CharField( + blank=True, + db_column="framedprotocol", + max_length=32, + null=True, + verbose_name="framed protocol", + ), + ), + ( + "framed_ip_address", + models.GenericIPAddressField( + blank=True, + db_column="framedipaddress", + db_index=True, + null=True, + verbose_name="framed IP address", + ), + ), + ( + "update_time", + models.DateTimeField( + blank=True, + db_column="acctupdatetime", + null=True, + verbose_name="update time", + ), + ), + ( + "interval", + models.IntegerField( + blank=True, + db_column="acctinterval", + null=True, + verbose_name="interval", + ), + ), + ( + "organization", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + ], + options={ + "swappable": "OPENWISP_RADIUS_RADIUSACCOUNTING_MODEL", + "db_table": "radacct", + "verbose_name_plural": "accountings", + "abstract": False, + "verbose_name": "accounting", + }, + bases=(openwisp_users.mixins.ValidateOrgMixin, models.Model), + ), + migrations.CreateModel( + name="RadiusCheck", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "username", + models.CharField( + blank=True, + db_index=True, + max_length=64, + verbose_name="username", + ), + ), + ("value", models.CharField(max_length=253, verbose_name="value")), + ( + "op", + models.CharField( + choices=[ + ("=", "="), + (":=", ":="), + ("==", "=="), + ("+=", "+="), + ("!=", "!="), + (">", ">"), + (">=", ">="), + ("<", "<"), + ("<=", "<="), + ("=~", "=~"), + ("!~", "!~"), + ("=*", "=*"), + ("!*", "!*"), + ], + default=":=", + max_length=2, + verbose_name="operator", + ), + ), + ( + "attribute", + models.CharField( + choices=[ + ("Max-Daily-Session", "Max-Daily-Session"), + ("Max-All-Session", "Max-All-Session"), + ("Max-Daily-Session-Traffic", "Max-Daily-Session-Traffic"), + ("Cleartext-Password", "Cleartext-Password"), + ("NT-Password", "NT-Password"), + ("LM-Password", "LM-Password"), + ("MD5-Password", "MD5-Password"), + ("SMD5-Password", "SMD5-Password"), + ("SHA-Password", "SHA-Password"), + ("SSHA-Password", "SSHA-Password"), + ("Crypt-Password", "Crypt-Password"), + ], + default="NT-Password", + max_length=64, + verbose_name="attribute", + ), + ), + ("is_active", models.BooleanField(default=True)), + ("notes", models.TextField(blank=True, null=True)), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ("valid_until", models.DateTimeField(blank=True, null=True)), + ( + "organization", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + ], + options={ + "swappable": "OPENWISP_RADIUS_RADIUSCHECK_MODEL", + "db_table": "radcheck", + "verbose_name_plural": "checks", + "abstract": False, + "verbose_name": "check", + }, + bases=( + openwisp_users.mixins.ValidateOrgMixin, + openwisp_radius.base.models.AutoUsernameMixin, + models.Model, + ), + ), + migrations.CreateModel( + name="RadiusPostAuth", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("username", models.CharField(max_length=64, verbose_name="username")), + ( + "password", + models.CharField( + blank=True, + db_column="pass", + max_length=64, + verbose_name="password", + ), + ), + ("reply", models.CharField(max_length=32, verbose_name="reply")), + ( + "date", + models.DateTimeField( + auto_now_add=True, db_column="authdate", verbose_name="date" + ), + ), + ( + "called_station_id", + models.CharField( + blank=True, + db_column="calledstationid", + max_length=50, + null=True, + verbose_name="called station ID", + ), + ), + ( + "calling_station_id", + models.CharField( + blank=True, + db_column="callingstationid", + max_length=50, + null=True, + verbose_name="calling station ID", + ), + ), + ( + "organization", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + ], + options={ + "swappable": "OPENWISP_RADIUS_RADIUSPOSTAUTH_MODEL", + "db_table": "radpostauth", + "verbose_name_plural": "post auth log", + "abstract": False, + "verbose_name": "post auth", + }, + bases=(openwisp_users.mixins.ValidateOrgMixin, models.Model), + ), + migrations.CreateModel( + name="RadiusReply", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "username", + models.CharField( + blank=True, + db_index=True, + max_length=64, + verbose_name="username", + ), + ), + ("value", models.CharField(max_length=253, verbose_name="value")), + ( + "op", + models.CharField( + choices=[("=", "="), (":=", ":="), ("+=", "+=")], + default="=", + max_length=2, + verbose_name="operator", + ), + ), + ( + "attribute", + models.CharField(max_length=64, verbose_name="attribute"), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "organization", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + ], + options={ + "swappable": "OPENWISP_RADIUS_RADIUSREPLY_MODEL", + "db_table": "radreply", + "verbose_name_plural": "replies", + "abstract": False, + "verbose_name": "reply", + }, + bases=( + openwisp_users.mixins.ValidateOrgMixin, + openwisp_radius.base.models.AutoUsernameMixin, + models.Model, + ), + ), + migrations.RunPython( + code=openwisp_radius.migrations.add_default_organization, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.AlterField( + model_name="nas", + name="organization", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + migrations.AlterField( + model_name="radiusaccounting", + name="organization", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + migrations.AlterField( + model_name="radiuscheck", + name="organization", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + migrations.AlterField( + model_name="radiuspostauth", + name="organization", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + migrations.AlterField( + model_name="radiusreply", + name="organization", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + migrations.RunPython( + code=openwisp_radius.migrations.add_default_groups, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.RunPython( + code=openwisp_radius.migrations.add_default_group_to_existing_users, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.RunPython( + code=openwisp_radius.migrations.assign_permissions_to_groups, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.CreateModel( + name="RadiusToken", + fields=[ + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "key", + models.CharField( + max_length=40, + primary_key=True, + serialize=False, + verbose_name="Key", + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="radius_token", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "radius token", + "verbose_name_plural": "radius token", + "db_table": "radiustoken", + "abstract": False, + "swappable": "OPENWISP_RADIUS_RADIUSTOKEN_MODEL", + }, + ), + migrations.AddField( + model_name="radiusaccounting", + name="delegated_ipv6_prefix", + field=models.CharField( + blank=True, + db_column="delegatedipv6prefix", + max_length=44, + null=True, + validators=[openwisp_radius.base.validators.ipv6_network_validator], + verbose_name="delegated IPv6 prefix", + ), + ), + migrations.AddField( + model_name="radiusaccounting", + name="framed_interface_id", + field=models.CharField( + blank=True, + db_column="framedinterfaceid", + max_length=19, + null=True, + verbose_name="framed interface ID", + ), + ), + migrations.AddField( + model_name="radiusaccounting", + name="framed_ipv6_address", + field=models.GenericIPAddressField( + blank=True, + db_column="framedipv6address", + null=True, + protocol="IPv6", + verbose_name="framed IPv6 address", + ), + ), + migrations.AddField( + model_name="radiusaccounting", + name="framed_ipv6_prefix", + field=models.CharField( + blank=True, + db_column="framedipv6prefix", + max_length=44, + null=True, + validators=[openwisp_radius.base.validators.ipv6_network_validator], + verbose_name="framed IPv6 prefix", + ), + ), + migrations.AlterField( + model_name="radiusaccounting", + name="framed_ip_address", + field=models.GenericIPAddressField( + blank=True, + db_column="framedipaddress", + null=True, + verbose_name="framed IP address", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="sms_verification", + field=models.BooleanField( + default=False, + help_text=( + "whether users who sign up should be required to verify their" + " mobile phone number via SMS" + ), + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="sms_meta_data", + field=models.TextField( + blank=True, + help_text=( + "Additional configuration for SMS backend in JSON format" + " (optional, leave blank if unsure)" + ), + null=True, + verbose_name="SMS meta data", + ), + ), + migrations.CreateModel( + name="PhoneToken", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created", + model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="modified", + ), + ), + ( + "valid_until", + models.DateTimeField( + default=openwisp_radius.utils.get_sms_default_valid_until + ), + ), + ("attempts", models.PositiveIntegerField(default=0)), + ("verified", models.BooleanField(default=False)), + ( + "token", + models.CharField( + default=openwisp_radius.utils.generate_sms_token, + editable=False, + max_length=8, + ), + ), + ("ip", models.GenericIPAddressField()), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "Phone verification token", + "ordering": ("-created",), + "verbose_name_plural": "Phone verification tokens", + "indexes": [ + models.Index( + fields=["user", "created"], + name="openwisp_ra_user_id_9fe207_idx", + ), + models.Index( + fields=["user", "created", "ip"], + name="openwisp_ra_user_id_d4dd52_idx", + ), + ], + }, + ), + migrations.AddField( + model_name="organizationradiussettings", + name="sms_sender", + field=models.CharField( + blank=True, + help_text=( + "alpha numeric identifier used as sender for SMS sent by this" + " organization" + ), + max_length=128, + null=True, + verbose_name="Sender", + ), + ), + migrations.RemoveField( + model_name="radiusbatch", + name="pdf", + ), + migrations.AddField( + model_name="radiusbatch", + name="user_credentials", + field=models.TextField(blank=True, null=True, verbose_name="PDF"), + ), + migrations.AlterField( + model_name="radiusbatch", + name="csvfile", + field=private_storage.fields.PrivateFileField( + blank=True, + help_text="The csv file containing the user details to be uploaded", + null=True, + storage=private_storage.storage.files.PrivateFileSystemStorage( + base_url=urljoin( + app_settings.RADIUS_API_BASEURL, app_settings.CSV_URL_PATH + ), + location=settings.PRIVATE_STORAGE_ROOT, + ), + upload_to=openwisp_radius.base.models._get_csv_file_location, + verbose_name="CSV", + ), + ), + migrations.AddField( + model_name="nas", + name="uuid", + field=models.UUIDField( + default=uuid.uuid4, editable=False, null=True, serialize=False + ), + ), + migrations.AddField( + model_name="radiuscheck", + name="uuid", + field=models.UUIDField( + default=uuid.uuid4, editable=False, null=True, serialize=False + ), + ), + migrations.AddField( + model_name="radiusgroupcheck", + name="uuid", + field=models.UUIDField( + default=uuid.uuid4, editable=False, null=True, serialize=False + ), + ), + migrations.AddField( + model_name="radiusgroupreply", + name="uuid", + field=models.UUIDField( + default=uuid.uuid4, editable=False, null=True, serialize=False + ), + ), + migrations.AddField( + model_name="radiuspostauth", + name="uuid", + field=models.UUIDField( + default=uuid.uuid4, editable=False, null=True, serialize=False + ), + ), + migrations.AddField( + model_name="radiusreply", + name="uuid", + field=models.UUIDField( + default=uuid.uuid4, editable=False, null=True, serialize=False + ), + ), + migrations.AddField( + model_name="radiususergroup", + name="uuid", + field=models.UUIDField( + default=uuid.uuid4, editable=False, null=True, serialize=False + ), + ), + migrations.RunPython( + code=openwisp_radius.migrations.popluate_uuids, + ), + migrations.RemoveField( + model_name="nas", + name="id", + ), + migrations.RemoveField( + model_name="radiusaccounting", + name="id", + ), + migrations.RemoveField( + model_name="radiuscheck", + name="id", + ), + migrations.RemoveField( + model_name="radiusgroupcheck", + name="id", + ), + migrations.RemoveField( + model_name="radiusgroupreply", + name="id", + ), + migrations.RemoveField( + model_name="radiuspostauth", + name="id", + ), + migrations.RemoveField( + model_name="radiusreply", + name="id", + ), + migrations.RemoveField( + model_name="radiususergroup", + name="id", + ), + migrations.AlterField( + model_name="radiusaccounting", + name="unique_id", + field=models.CharField( + db_column="acctuniqueid", + max_length=32, + primary_key=True, + serialize=False, + unique=True, + verbose_name="accounting unique ID", + ), + ), + migrations.RenameField( + model_name="nas", + old_name="uuid", + new_name="id", + ), + migrations.AlterField( + model_name="nas", + name="id", + field=models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False + ), + ), + migrations.RenameField( + model_name="radiuscheck", + old_name="uuid", + new_name="id", + ), + migrations.AlterField( + model_name="radiuscheck", + name="id", + field=models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False + ), + ), + migrations.RenameField( + model_name="radiusgroupcheck", + old_name="uuid", + new_name="id", + ), + migrations.AlterField( + model_name="radiusgroupcheck", + name="id", + field=models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False + ), + ), + migrations.RenameField( + model_name="radiusgroupreply", + old_name="uuid", + new_name="id", + ), + migrations.AlterField( + model_name="radiusgroupreply", + name="id", + field=models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False + ), + ), + migrations.RenameField( + model_name="radiuspostauth", + old_name="uuid", + new_name="id", + ), + migrations.AlterField( + model_name="radiuspostauth", + name="id", + field=models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False + ), + ), + migrations.RenameField( + model_name="radiusreply", + old_name="uuid", + new_name="id", + ), + migrations.AlterField( + model_name="radiusreply", + name="id", + field=models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False + ), + ), + migrations.RenameField( + model_name="radiususergroup", + old_name="uuid", + new_name="id", + ), + migrations.AlterField( + model_name="radiususergroup", + name="id", + field=models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False + ), + ), + migrations.RunPython( + code=openwisp_radius.migrations.delete_old_radius_token, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.AddField( + model_name="radiustoken", + name="can_auth", + field=models.BooleanField( + default=False, + help_text=( + "Enable the radius token to be used for freeradius authorization" + " request" + ), + ), + ), + migrations.AddField( + model_name="radiustoken", + name="organization", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="openwisp_users.organization", + verbose_name="organization", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="freeradius_allowed_hosts", + field=models.TextField( + blank=True, + help_text=( + "Comma separated list of IP addresses allowed to access freeradius" + " API" + ), + null=True, + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="allowed_mobile_prefixes", + field=models.TextField( + blank=True, + help_text=( + "Comma separated list of international mobile prefixes allowed to" + " register via the user registration API." + ), + null=True, + ), + ), + migrations.AddField( + model_name="phonetoken", + name="phone_number", + field=phonenumber_field.modelfields.PhoneNumberField( + blank=True, max_length=128, null=True, region=None + ), + ), + migrations.RunPython( + code=openwisp_radius.migrations.populate_phonetoken_phone_number, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.AlterField( + model_name="phonetoken", + name="phone_number", + field=phonenumber_field.modelfields.PhoneNumberField( + max_length=128, region=None + ), + ), + migrations.AlterUniqueTogether( + name="radiususergroup", + unique_together={("user", "group")}, + ), + migrations.CreateModel( + name="RegisteredUser", + fields=[ + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + primary_key=True, + related_name="registered_user", + serialize=False, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "method", + models.CharField( + blank=True, + choices=openwisp_radius.registration.get_registration_choices, + default="", + help_text=( + "users can sign up in different ways, some methods are" + " valid as indirect identity verification (eg: mobile" + " phone SIM card in most countries)" + ), + max_length=64, + verbose_name="registration method", + ), + ), + ( + "is_verified", + models.BooleanField( + default=False, + help_text=( + "whether the user has completed any identity verification" + " process sucessfully" + ), + verbose_name="verified", + ), + ), + ( + "modified", + model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, + editable=False, + verbose_name="Last verification change", + ), + ), + ], + options={ + "verbose_name": "Registration Information", + "verbose_name_plural": "Registration Information", + "abstract": False, + "swappable": "OPENWISP_RADIUS_REGISTEREDUSER_MODEL", + }, + ), + migrations.AddField( + model_name="organizationradiussettings", + name="login_url", + field=models.URLField( + blank=True, + help_text="Enter the URL where users can log in to the wifi service", + null=True, + verbose_name="Login URL", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="status_url", + field=models.URLField( + blank=True, + help_text="Enter the URL where users can log out from the wifi service", + null=True, + verbose_name="Status URL", + ), + ), + migrations.RemoveField( + model_name="radiuscheck", + name="is_active", + ), + migrations.RemoveField( + model_name="radiuscheck", + name="valid_until", + ), + migrations.AlterField( + model_name="radiuscheck", + name="attribute", + field=models.CharField(max_length=64, verbose_name="attribute"), + ), + migrations.RemoveField( + model_name="radiuscheck", + name="notes", + ), + migrations.AddField( + model_name="organizationradiussettings", + name="birth_date", + field=openwisp_utils.fields.FallbackCharChoiceField( + blank=True, + choices=[ + ("disabled", "Disabled"), + ("allowed", "Allowed"), + ("mandatory", "Mandatory"), + ], + default=None, + fallback="disabled", + help_text=( + "Whether this field should be disabled, allowed or mandatory in" + " the user registration API." + ), + max_length=12, + null=True, + verbose_name="birth date", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="first_name", + field=openwisp_utils.fields.FallbackCharChoiceField( + blank=True, + choices=[ + ("disabled", "Disabled"), + ("allowed", "Allowed"), + ("mandatory", "Mandatory"), + ], + default=None, + fallback="disabled", + help_text=( + "Whether this field should be disabled, allowed or mandatory in" + " the user registration API." + ), + max_length=12, + null=True, + verbose_name="first name", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="last_name", + field=openwisp_utils.fields.FallbackCharChoiceField( + blank=True, + choices=[ + ("disabled", "Disabled"), + ("allowed", "Allowed"), + ("mandatory", "Mandatory"), + ], + default=None, + fallback="disabled", + help_text=( + "Whether this field should be disabled, allowed or mandatory in" + " the user registration API." + ), + max_length=12, + null=True, + verbose_name="last name", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="location", + field=openwisp_utils.fields.FallbackCharChoiceField( + blank=True, + choices=[ + ("disabled", "Disabled"), + ("allowed", "Allowed"), + ("mandatory", "Mandatory"), + ], + default=None, + fallback="disabled", + help_text=( + "Whether this field should be disabled, allowed or mandatory in" + " the user registration API." + ), + max_length=12, + null=True, + verbose_name="location", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="needs_identity_verification", + field=openwisp_utils.fields.FallbackBooleanChoiceField( + blank=True, + default=None, + fallback=False, + help_text=( + "Whether identity verification is required at the time of user" + " registration" + ), + null=True, + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="registration_enabled", + field=openwisp_utils.fields.FallbackBooleanChoiceField( + blank=True, + default=None, + fallback=True, + help_text=( + "Whether the registration API endpoint should be enabled or not" + ), + null=True, + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="saml_registration_enabled", + field=openwisp_utils.fields.FallbackBooleanChoiceField( + blank=True, + default=None, + fallback=False, + help_text=( + "Whether the registration using SAML should be enabled or not" + ), + null=True, + verbose_name="SAML registration enabled", + ), + ), + migrations.AlterField( + model_name="organizationradiussettings", + name="sms_verification", + field=openwisp_utils.fields.FallbackBooleanChoiceField( + blank=True, + default=None, + fallback=False, + help_text=( + "Whether users who sign up should be required to verify their" + " mobile phone number via SMS" + ), + null=True, + verbose_name="SMS verification", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="social_registration_enabled", + field=openwisp_utils.fields.FallbackBooleanChoiceField( + blank=True, + default=None, + fallback=False, + help_text=( + "Whether the registration using social applications should be" + " enabled or not" + ), + null=True, + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="sms_message", + field=openwisp_utils.fields.FallbackTextField( + blank=True, + default=None, + fallback="{organization} verification code: {code}", + help_text=( + "SMS message template used for sending verification code. Must" + ' contain "{code}" placeholder for OTP value.' + ), + max_length=160, + null=True, + verbose_name="SMS Message", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="coa_enabled", + field=openwisp_utils.fields.FallbackBooleanChoiceField( + blank=True, + default=None, + fallback=True, + help_text="Whether RADIUS Change Of Authoization (CoA) is enabled", + null=True, + verbose_name="CoA Enabled", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="sms_cooldown", + field=openwisp_utils.fields.FallbackPositiveIntegerField( + blank=True, + default=None, + fallback=30, + help_text=( + "Time period a user will have to wait before requesting another" + " SMS token (in seconds)." + ), + null=True, + verbose_name="SMS Cooldown", + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="mac_addr_roaming_enabled", + field=openwisp_utils.fields.FallbackBooleanChoiceField( + blank=True, + default=None, + fallback=False, + help_text="Whether the MAC address roaming should be enabled or not.", + null=True, + verbose_name="MAC address roaming enabled", + ), + ), + migrations.AlterField( + model_name="organizationradiussettings", + name="allowed_mobile_prefixes", + field=openwisp_utils.fields.FallbackTextField( + blank=True, + default=None, + fallback="+44,+39,+237,+595", + help_text=( + "Comma separated list of international mobile prefixes allowed to" + " register via the user registration API." + ), + null=True, + ), + ), + migrations.AlterField( + model_name="organizationradiussettings", + name="freeradius_allowed_hosts", + field=openwisp_utils.fields.FallbackTextField( + blank=True, + default=None, + fallback="127.0.0.1", + help_text=( + "Comma separated list of IP addresses allowed to access freeradius" + " API" + ), + null=True, + ), + ), + migrations.AddField( + model_name="organizationradiussettings", + name="password_reset_url", + field=openwisp_utils.fields.FallbackCharField( + blank=True, + default=None, + fallback=( + "http://localhost:8080/{organization}/password/reset/confirm/{uid}/" + "{token}" + ), + help_text="Enter the URL where users can reset their password", + max_length=200, + null=True, + validators=[ + openwisp_radius.base.validators.password_reset_url_validator + ], + verbose_name="Password reset URL", + ), + ), + migrations.RunPython( + code=clean_fallback_fields, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.AlterField( + model_name="radiusaccounting", + name="called_station_id", + field=models.CharField( + blank=True, + db_column="calledstationid", + db_index=True, + max_length=253, + null=True, + verbose_name="called station ID", + ), + ), + migrations.AlterField( + model_name="radiusaccounting", + name="calling_station_id", + field=models.CharField( + blank=True, + db_column="callingstationid", + db_index=True, + max_length=253, + null=True, + verbose_name="calling station ID", + ), + ), + migrations.AlterField( + model_name="radiuspostauth", + name="called_station_id", + field=models.CharField( + blank=True, + db_column="calledstationid", + max_length=253, + null=True, + verbose_name="called station ID", + ), + ), + migrations.AlterField( + model_name="radiuspostauth", + name="calling_station_id", + field=models.CharField( + blank=True, + db_column="callingstationid", + max_length=253, + null=True, + verbose_name="calling station ID", + ), + ), + migrations.RenameIndex( + model_name="phonetoken", + new_name="openwisp_ra_user_id_9fe207_idx", + old_fields=("user", "created"), + ), + migrations.RenameIndex( + model_name="phonetoken", + new_name="openwisp_ra_user_id_d4dd52_idx", + old_fields=("user", "created", "ip"), + ), + migrations.AddField( + model_name="radiusbatch", + name="status", + field=models.CharField( + choices=[ + ("pending", "Pending"), + ("processing", "Processing"), + ("completed", "Completed"), + ("failed", "Failed"), + ], + db_index=True, + default="pending", + max_length=16, + ), + ), + migrations.RunPython( + code=set_existing_batches_completed, + ), + ] diff --git a/openwisp_radius/migrations/__init__.py b/openwisp_radius/migrations/__init__.py index 22aa36c1..3ecfedc0 100644 --- a/openwisp_radius/migrations/__init__.py +++ b/openwisp_radius/migrations/__init__.py @@ -343,11 +343,17 @@ def migrate_registered_users_multitenant_reverse( RegisteredUser.objects.filter(pk__in=to_delete_pks).delete() +# Flagged for removal (#705): data migration for the squashed 0001-0042 range +# (released <= 1.2.x). Remove in the future cleanup that deletes the original +# 0001-0042 files and drops their RunPython steps from the squashed migration. def delete_old_radius_token(apps, schema_editor): RadiusToken = get_swapped_model(apps, "openwisp_radius", "RadiusToken") RadiusToken.objects.all().delete() +# Flagged for removal (#705): data migration for the squashed 0001-0042 range +# (released <= 1.2.x). Remove in the future cleanup that deletes the original +# 0001-0042 files and drops their RunPython steps from the squashed migration. def add_default_organization(apps, schema_editor): """ Set default organization using @@ -371,6 +377,9 @@ def add_default_organization(apps, schema_editor): OrganizationRadiusSettings.objects.create(organization_id=default_org_id) +# Flagged for removal (#705): data migration for the squashed 0001-0042 range +# (released <= 1.2.x). Remove in the future cleanup that deletes the original +# 0001-0042 files and drops their RunPython steps from the squashed migration. def add_default_groups(apps, schema_editor): Organization = get_swapped_model(apps, "openwisp_users", "Organization") RadiusGroup = get_swapped_model(apps, "openwisp_radius", "RadiusGroup") @@ -379,6 +388,9 @@ def add_default_groups(apps, schema_editor): create_default_groups(organization, apps=apps) +# Flagged for removal (#705): data migration for the squashed 0001-0042 range +# (released <= 1.2.x). Remove in the future cleanup that deletes the original +# 0001-0042 files and drops their RunPython steps from the squashed migration. def add_default_group_to_existing_users(apps, schema_editor): Organization = get_swapped_model(apps, "openwisp_users", "Organization") OrganizationUser = get_swapped_model(apps, "openwisp_users", "OrganizationUser") @@ -403,6 +415,10 @@ def add_default_group_to_existing_users(apps, schema_editor): user_group.save() +# Flagged for removal (#705): helper for assign_permissions_to_groups, used only +# by the squashed 0001-0042 range (released <= 1.2.x). Remove in the future +# cleanup that deletes the original 0001-0042 files and drops their RunPython +# steps from the squashed migration. def create_default_permissions(apps, schema_editor): for app_config in apps.get_app_configs(): app_config.models_module = True @@ -410,6 +426,9 @@ def create_default_permissions(apps, schema_editor): app_config.models_module = None +# Flagged for removal (#705): data migration for the squashed 0001-0042 range +# (released <= 1.2.x). Remove in the future cleanup that deletes the original +# 0001-0042 files and drops their RunPython steps from the squashed migration. def assign_permissions_to_groups(apps, schema_editor): create_default_permissions(apps, schema_editor) Group = get_swapped_model(apps, "openwisp_users", "Group") @@ -452,6 +471,9 @@ def assign_permissions_to_groups(apps, schema_editor): admin.permissions.add(permission_ad.pk) +# Flagged for removal (#705): data migration for the squashed 0001-0042 range +# (released <= 1.2.x). Remove in the future cleanup that deletes the original +# 0001-0042 files and drops their RunPython steps from the squashed migration. def popluate_uuids(apps, schema_editor): models = [ "RadiusCheck", @@ -469,6 +491,9 @@ def popluate_uuids(apps, schema_editor): obj.save(update_fields=["uuid"]) +# Flagged for removal (#705): data migration for the squashed 0001-0042 range +# (released <= 1.2.x). Remove in the future cleanup that deletes the original +# 0001-0042 files and drops their RunPython steps from the squashed migration. def populate_phonetoken_phone_number(apps, schema_editor): PhoneToken = get_swapped_model(apps, "openwisp_radius", "PhoneToken") for phone_token in PhoneToken.objects.all(): From 345a0499d5398dc032ed2fe6940ac3af2e02c521 Mon Sep 17 00:00:00 2001 From: CodingWithSaksham Date: Fri, 26 Jun 2026 23:35:26 +0530 Subject: [PATCH 2/3] [fix] Fixed squashed migration for Django 4.2 --- .../migrations/0001_squashed_0042_initial.py | 238 +++--------------- 1 file changed, 29 insertions(+), 209 deletions(-) diff --git a/openwisp_radius/migrations/0001_squashed_0042_initial.py b/openwisp_radius/migrations/0001_squashed_0042_initial.py index 651d0538..952a27b2 100644 --- a/openwisp_radius/migrations/0001_squashed_0042_initial.py +++ b/openwisp_radius/migrations/0001_squashed_0042_initial.py @@ -65,7 +65,6 @@ def set_existing_batches_completed(apps, schema_editor): class Migration(migrations.Migration): - replaces = [ ("openwisp_radius", "0001_initial_freeradius"), ("openwisp_radius", "0002_initial_openwisp_radius"), @@ -207,11 +206,11 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( - auto_created=True, + models.UUIDField( + default=uuid.uuid4, + editable=False, primary_key=True, serialize=False, - verbose_name="ID", ), ), ( @@ -291,11 +290,11 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( - auto_created=True, + models.UUIDField( + default=uuid.uuid4, + editable=False, primary_key=True, serialize=False, - verbose_name="ID", ), ), ( @@ -361,11 +360,11 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( - auto_created=True, + models.UUIDField( + default=uuid.uuid4, + editable=False, primary_key=True, serialize=False, - verbose_name="ID", ), ), ( @@ -607,11 +606,11 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( - auto_created=True, + models.UUIDField( + default=uuid.uuid4, + editable=False, primary_key=True, serialize=False, - verbose_name="ID", ), ), ( @@ -773,12 +772,6 @@ class Migration(migrations.Migration): migrations.CreateModel( name="RadiusAccounting", fields=[ - ( - "id", - models.BigAutoField( - db_column="radacctid", primary_key=True, serialize=False - ), - ), ( "session_id", models.CharField( @@ -793,6 +786,8 @@ class Migration(migrations.Migration): models.CharField( db_column="acctuniqueid", max_length=32, + primary_key=True, + serialize=False, unique=True, verbose_name="accounting unique ID", ), @@ -1028,11 +1023,11 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( - auto_created=True, + models.UUIDField( + default=uuid.uuid4, + editable=False, primary_key=True, serialize=False, - verbose_name="ID", ), ), ( @@ -1145,11 +1140,11 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( - auto_created=True, + models.UUIDField( + default=uuid.uuid4, + editable=False, primary_key=True, serialize=False, - verbose_name="ID", ), ), ("username", models.CharField(max_length=64, verbose_name="username")), @@ -1213,11 +1208,11 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( - auto_created=True, + models.UUIDField( + default=uuid.uuid4, + editable=False, primary_key=True, serialize=False, - verbose_name="ID", ), ), ( @@ -1578,186 +1573,6 @@ class Migration(migrations.Migration): verbose_name="CSV", ), ), - migrations.AddField( - model_name="nas", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, null=True, serialize=False - ), - ), - migrations.AddField( - model_name="radiuscheck", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, null=True, serialize=False - ), - ), - migrations.AddField( - model_name="radiusgroupcheck", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, null=True, serialize=False - ), - ), - migrations.AddField( - model_name="radiusgroupreply", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, null=True, serialize=False - ), - ), - migrations.AddField( - model_name="radiuspostauth", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, null=True, serialize=False - ), - ), - migrations.AddField( - model_name="radiusreply", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, null=True, serialize=False - ), - ), - migrations.AddField( - model_name="radiususergroup", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, null=True, serialize=False - ), - ), - migrations.RunPython( - code=openwisp_radius.migrations.popluate_uuids, - ), - migrations.RemoveField( - model_name="nas", - name="id", - ), - migrations.RemoveField( - model_name="radiusaccounting", - name="id", - ), - migrations.RemoveField( - model_name="radiuscheck", - name="id", - ), - migrations.RemoveField( - model_name="radiusgroupcheck", - name="id", - ), - migrations.RemoveField( - model_name="radiusgroupreply", - name="id", - ), - migrations.RemoveField( - model_name="radiuspostauth", - name="id", - ), - migrations.RemoveField( - model_name="radiusreply", - name="id", - ), - migrations.RemoveField( - model_name="radiususergroup", - name="id", - ), - migrations.AlterField( - model_name="radiusaccounting", - name="unique_id", - field=models.CharField( - db_column="acctuniqueid", - max_length=32, - primary_key=True, - serialize=False, - unique=True, - verbose_name="accounting unique ID", - ), - ), - migrations.RenameField( - model_name="nas", - old_name="uuid", - new_name="id", - ), - migrations.AlterField( - model_name="nas", - name="id", - field=models.UUIDField( - default=uuid.uuid4, editable=False, primary_key=True, serialize=False - ), - ), - migrations.RenameField( - model_name="radiuscheck", - old_name="uuid", - new_name="id", - ), - migrations.AlterField( - model_name="radiuscheck", - name="id", - field=models.UUIDField( - default=uuid.uuid4, editable=False, primary_key=True, serialize=False - ), - ), - migrations.RenameField( - model_name="radiusgroupcheck", - old_name="uuid", - new_name="id", - ), - migrations.AlterField( - model_name="radiusgroupcheck", - name="id", - field=models.UUIDField( - default=uuid.uuid4, editable=False, primary_key=True, serialize=False - ), - ), - migrations.RenameField( - model_name="radiusgroupreply", - old_name="uuid", - new_name="id", - ), - migrations.AlterField( - model_name="radiusgroupreply", - name="id", - field=models.UUIDField( - default=uuid.uuid4, editable=False, primary_key=True, serialize=False - ), - ), - migrations.RenameField( - model_name="radiuspostauth", - old_name="uuid", - new_name="id", - ), - migrations.AlterField( - model_name="radiuspostauth", - name="id", - field=models.UUIDField( - default=uuid.uuid4, editable=False, primary_key=True, serialize=False - ), - ), - migrations.RenameField( - model_name="radiusreply", - old_name="uuid", - new_name="id", - ), - migrations.AlterField( - model_name="radiusreply", - name="id", - field=models.UUIDField( - default=uuid.uuid4, editable=False, primary_key=True, serialize=False - ), - ), - migrations.RenameField( - model_name="radiususergroup", - old_name="uuid", - new_name="id", - ), - migrations.AlterField( - model_name="radiususergroup", - name="id", - field=models.UUIDField( - default=uuid.uuid4, editable=False, primary_key=True, serialize=False - ), - ), migrations.RunPython( code=openwisp_radius.migrations.delete_old_radius_token, reverse_code=django.db.migrations.operations.special.RunPython.noop, @@ -1845,7 +1660,12 @@ class Migration(migrations.Migration): "method", models.CharField( blank=True, - choices=openwisp_radius.registration.get_registration_choices, + choices=( + openwisp_radius.registration.REGISTRATION_METHOD_CHOICES + if django.VERSION < (5, 0) + # TODO: Remove when dropping support for Django 4.2 + else openwisp_radius.registration.get_registration_choices + ), default="", help_text=( "users can sign up in different ways, some methods are" From d6f6ddc59f4107739cc911306dc02be2ecec133f Mon Sep 17 00:00:00 2001 From: CodingWithSaksham Date: Sun, 28 Jun 2026 17:44:54 +0530 Subject: [PATCH 3/3] [change] Remove no-op RunPython calls from squashed migration --- .../migrations/0001_squashed_0042_initial.py | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/openwisp_radius/migrations/0001_squashed_0042_initial.py b/openwisp_radius/migrations/0001_squashed_0042_initial.py index 952a27b2..e62345a5 100644 --- a/openwisp_radius/migrations/0001_squashed_0042_initial.py +++ b/openwisp_radius/migrations/0001_squashed_0042_initial.py @@ -25,43 +25,6 @@ import openwisp_utils.fields import openwisp_utils.utils from openwisp_radius import settings as app_settings -from openwisp_utils.fields import FallbackMixin - - -# Copied verbatim from 0038_clean_fallbackfields and 0042_set_existing_batches_completed -# because squashmigrations cannot import functions from modules whose names start -# with a digit. Keep these in sync if the originals ever change. -def clean_fallback_fields(apps, schema_editor): - OrganizationRadiusSettings = openwisp_radius.migrations.get_swapped_model( - apps, "openwisp_radius", "organizationradiussettings" - ) - fallback_fields = [] - for field in OrganizationRadiusSettings._meta.get_fields(): - if isinstance(field, FallbackMixin): - fallback_fields.append(field) - updated_settings = [] - for radius_settings in OrganizationRadiusSettings.objects.iterator(): - changed = False - for field in fallback_fields: - if getattr(radius_settings, field.name) == field.fallback: - setattr(radius_settings, field.name, None) - changed = True - if changed: - updated_settings.append(radius_settings) - if len(updated_settings) > 100: - OrganizationRadiusSettings.objects.bulk_update( - updated_settings, fields=[f.name for f in fallback_fields] - ) - updated_settings = [] - if updated_settings: - OrganizationRadiusSettings.objects.bulk_update( - updated_settings, fields=[f.name for f in fallback_fields] - ) - - -def set_existing_batches_completed(apps, schema_editor): - RadiusBatch = apps.get_model("openwisp_radius", "RadiusBatch") - RadiusBatch.objects.all().update(status="completed") class Migration(migrations.Migration): @@ -1339,10 +1302,6 @@ class Migration(migrations.Migration): code=openwisp_radius.migrations.add_default_groups, reverse_code=django.db.migrations.operations.special.RunPython.noop, ), - migrations.RunPython( - code=openwisp_radius.migrations.add_default_group_to_existing_users, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), migrations.RunPython( code=openwisp_radius.migrations.assign_permissions_to_groups, reverse_code=django.db.migrations.operations.special.RunPython.noop, @@ -1573,10 +1532,6 @@ class Migration(migrations.Migration): verbose_name="CSV", ), ), - migrations.RunPython( - code=openwisp_radius.migrations.delete_old_radius_token, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), migrations.AddField( model_name="radiustoken", name="can_auth", @@ -1628,10 +1583,6 @@ class Migration(migrations.Migration): blank=True, max_length=128, null=True, region=None ), ), - migrations.RunPython( - code=openwisp_radius.migrations.populate_phonetoken_phone_number, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), migrations.AlterField( model_name="phonetoken", name="phone_number", @@ -1996,10 +1947,6 @@ class Migration(migrations.Migration): verbose_name="Password reset URL", ), ), - migrations.RunPython( - code=clean_fallback_fields, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), migrations.AlterField( model_name="radiusaccounting", name="called_station_id", @@ -2071,7 +2018,4 @@ class Migration(migrations.Migration): max_length=16, ), ), - migrations.RunPython( - code=set_existing_batches_completed, - ), ]