diff --git a/src/registrar/admin.py b/src/registrar/admin.py index b62ea80b8..31f031456 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -290,6 +290,13 @@ class CustomLogEntryAdmin(LogEntryAdmin): # Return the field value without a link return f"{obj.content_type} - {obj.object_repr}" + # We name the custom prop 'created_at' because linter + # is not allowing a short_description attr on it + # This gets around the linter limitation, for now. + @admin.display(description=_("Created at")) + def created(self, obj): + return obj.timestamp + search_help_text = "Search by resource, changes, or user." change_form_template = "admin/change_form_no_submit.html" @@ -478,7 +485,7 @@ class MyUserAdmin(BaseUserAdmin): list_display = ( "username", - "email", + "overridden_email_field", "first_name", "last_name", # Group is a custom property defined within this file, @@ -487,6 +494,18 @@ class MyUserAdmin(BaseUserAdmin): "status", ) + # Renames inherited AbstractUser label 'email_address to 'email' + def formfield_for_dbfield(self, dbfield, **kwargs): + field = super().formfield_for_dbfield(dbfield, **kwargs) + if dbfield.name == "email": + field.label = "Email" + return field + + # Renames inherited AbstractUser column name 'email_address to 'email' + @admin.display(description=_("Email")) + def overridden_email_field(self, obj): + return obj.email + fieldsets = ( ( None, @@ -561,6 +580,7 @@ class MyUserAdmin(BaseUserAdmin): # this ordering effects the ordering of results # in autocomplete_fields for user ordering = ["first_name", "last_name", "email"] + search_help_text = "Search by first name, last name, or email." change_form_template = "django/admin/email_clipboard_change_form.html" @@ -651,7 +671,7 @@ class MyHostAdmin(AuditedAdmin): """Custom host admin class to use our inlines.""" search_fields = ["name", "domain__name"] - search_help_text = "Search by domain or hostname." + search_help_text = "Search by domain or host name." inlines = [HostIPInline] @@ -659,9 +679,9 @@ class ContactAdmin(ListHeaderAdmin): """Custom contact admin class to add search.""" search_fields = ["email", "first_name", "last_name"] - search_help_text = "Search by firstname, lastname or email." + search_help_text = "Search by first name, last name or email." list_display = [ - "contact", + "name", "email", "user_exists", ] @@ -690,7 +710,7 @@ class ContactAdmin(ListHeaderAdmin): # We name the custom prop 'contact' because linter # is not allowing a short_description attr on it # This gets around the linter limitation, for now. - def contact(self, obj: models.Contact): + def name(self, obj: models.Contact): """Duplicate the contact _str_""" if obj.first_name or obj.last_name: return obj.get_formatted_name() @@ -701,7 +721,7 @@ class ContactAdmin(ListHeaderAdmin): else: return "" - contact.admin_order_field = "first_name" # type: ignore + name.admin_order_field = "first_name" # type: ignore # Read only that we'll leverage for CISA Analysts analyst_readonly_fields = [ @@ -859,7 +879,7 @@ class UserDomainRoleAdmin(ListHeaderAdmin): "domain__name", "role", ] - search_help_text = "Search by firstname, lastname, email, domain, or role." + search_help_text = "Search by first name, last name, email, or domain." autocomplete_fields = ["user", "domain"] @@ -1655,6 +1675,7 @@ class DomainAdmin(ListHeaderAdmin): city.admin_order_field = "domain_info__city" # type: ignore + @admin.display(description=_("State / territory")) def state_territory(self, obj): return obj.domain_info.state_territory if obj.domain_info else None @@ -1969,6 +1990,11 @@ class DraftDomainAdmin(ListHeaderAdmin): # this ordering effects the ordering of results # in autocomplete_fields for user ordering = ["name"] + list_display = ["name"] + + @admin.display(description=_("Requested domain")) + def name(self, obj): + return obj.name def get_model_perms(self, request): """ @@ -2047,13 +2073,36 @@ class FederalAgencyAdmin(ListHeaderAdmin): ordering = ["agency"] +class UserGroupAdmin(AuditedAdmin): + """Overwrite the generated UserGroup admin class""" + + list_display = ["user_group"] + + fieldsets = ((None, {"fields": ("name", "permissions")}),) + + def formfield_for_dbfield(self, dbfield, **kwargs): + field = super().formfield_for_dbfield(dbfield, **kwargs) + if dbfield.name == "name": + field.label = "Group name" + if dbfield.name == "permissions": + field.label = "User permissions" + return field + + # We name the custom prop 'Group' because linter + # is not allowing a short_description attr on it + # This gets around the linter limitation, for now. + @admin.display(description=_("Group")) + def user_group(self, obj): + return obj.name + + admin.site.unregister(LogEntry) # Unregister the default registration admin.site.register(LogEntry, CustomLogEntryAdmin) admin.site.register(models.User, MyUserAdmin) # Unregister the built-in Group model admin.site.unregister(Group) # Register UserGroup -admin.site.register(models.UserGroup) +admin.site.register(models.UserGroup, UserGroupAdmin) admin.site.register(models.UserDomainRole, UserDomainRoleAdmin) admin.site.register(models.Contact, ContactAdmin) admin.site.register(models.DomainInvitation, DomainInvitationAdmin) diff --git a/src/registrar/migrations/0085_alter_contact_first_name_alter_contact_last_name_and_more.py b/src/registrar/migrations/0085_alter_contact_first_name_alter_contact_last_name_and_more.py new file mode 100644 index 000000000..a0365c284 --- /dev/null +++ b/src/registrar/migrations/0085_alter_contact_first_name_alter_contact_last_name_and_more.py @@ -0,0 +1,382 @@ +# Generated by Django 4.2.10 on 2024-04-18 18:01 + +import django.core.validators +from django.db import migrations, models +import django_fsm +import registrar.models.utility.domain_field + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0084_create_groups_v11"), + ] + + operations = [ + migrations.AlterField( + model_name="contact", + name="first_name", + field=models.CharField(blank=True, db_index=True, null=True, verbose_name="first name"), + ), + migrations.AlterField( + model_name="contact", + name="last_name", + field=models.CharField(blank=True, db_index=True, null=True, verbose_name="last name"), + ), + migrations.AlterField( + model_name="contact", + name="title", + field=models.CharField(blank=True, null=True, verbose_name="title / role"), + ), + migrations.AlterField( + model_name="domain", + name="deleted", + field=models.DateField(editable=False, help_text="Deleted at date", null=True, verbose_name="deleted on"), + ), + migrations.AlterField( + model_name="domain", + name="first_ready", + field=models.DateField( + editable=False, + help_text="The last time this domain moved into the READY state", + null=True, + verbose_name="first ready on", + ), + ), + migrations.AlterField( + model_name="domain", + name="name", + field=registrar.models.utility.domain_field.DomainField( + default=None, + help_text="Fully qualified domain name", + max_length=253, + unique=True, + verbose_name="domain", + ), + ), + migrations.AlterField( + model_name="domain", + name="state", + field=django_fsm.FSMField( + choices=[ + ("unknown", "Unknown"), + ("dns needed", "Dns needed"), + ("ready", "Ready"), + ("on hold", "On hold"), + ("deleted", "Deleted"), + ], + default="unknown", + help_text="Very basic info about the lifecycle of this domain object", + max_length=21, + protected=True, + verbose_name="domain state", + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="address_line1", + field=models.CharField(blank=True, help_text="Street address", null=True, verbose_name="address line 1"), + ), + migrations.AlterField( + model_name="domaininformation", + name="address_line2", + field=models.CharField( + blank=True, help_text="Street address line 2 (optional)", null=True, verbose_name="address line 2" + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="is_election_board", + field=models.BooleanField( + blank=True, + help_text="Is your organization an election office?", + null=True, + verbose_name="election office", + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="state_territory", + field=models.CharField( + blank=True, + choices=[ + ("AL", "Alabama (AL)"), + ("AK", "Alaska (AK)"), + ("AS", "American Samoa (AS)"), + ("AZ", "Arizona (AZ)"), + ("AR", "Arkansas (AR)"), + ("CA", "California (CA)"), + ("CO", "Colorado (CO)"), + ("CT", "Connecticut (CT)"), + ("DE", "Delaware (DE)"), + ("DC", "District of Columbia (DC)"), + ("FL", "Florida (FL)"), + ("GA", "Georgia (GA)"), + ("GU", "Guam (GU)"), + ("HI", "Hawaii (HI)"), + ("ID", "Idaho (ID)"), + ("IL", "Illinois (IL)"), + ("IN", "Indiana (IN)"), + ("IA", "Iowa (IA)"), + ("KS", "Kansas (KS)"), + ("KY", "Kentucky (KY)"), + ("LA", "Louisiana (LA)"), + ("ME", "Maine (ME)"), + ("MD", "Maryland (MD)"), + ("MA", "Massachusetts (MA)"), + ("MI", "Michigan (MI)"), + ("MN", "Minnesota (MN)"), + ("MS", "Mississippi (MS)"), + ("MO", "Missouri (MO)"), + ("MT", "Montana (MT)"), + ("NE", "Nebraska (NE)"), + ("NV", "Nevada (NV)"), + ("NH", "New Hampshire (NH)"), + ("NJ", "New Jersey (NJ)"), + ("NM", "New Mexico (NM)"), + ("NY", "New York (NY)"), + ("NC", "North Carolina (NC)"), + ("ND", "North Dakota (ND)"), + ("MP", "Northern Mariana Islands (MP)"), + ("OH", "Ohio (OH)"), + ("OK", "Oklahoma (OK)"), + ("OR", "Oregon (OR)"), + ("PA", "Pennsylvania (PA)"), + ("PR", "Puerto Rico (PR)"), + ("RI", "Rhode Island (RI)"), + ("SC", "South Carolina (SC)"), + ("SD", "South Dakota (SD)"), + ("TN", "Tennessee (TN)"), + ("TX", "Texas (TX)"), + ("UM", "United States Minor Outlying Islands (UM)"), + ("UT", "Utah (UT)"), + ("VT", "Vermont (VT)"), + ("VI", "Virgin Islands (VI)"), + ("VA", "Virginia (VA)"), + ("WA", "Washington (WA)"), + ("WV", "West Virginia (WV)"), + ("WI", "Wisconsin (WI)"), + ("WY", "Wyoming (WY)"), + ("AA", "Armed Forces Americas (AA)"), + ("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"), + ("AP", "Armed Forces Pacific (AP)"), + ], + help_text="State, territory, or military post", + max_length=2, + null=True, + verbose_name="state / territory", + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="urbanization", + field=models.CharField( + blank=True, + help_text="Urbanization (required for Puerto Rico only)", + null=True, + verbose_name="urbanization", + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="zipcode", + field=models.CharField( + blank=True, db_index=True, help_text="Zip code", max_length=10, null=True, verbose_name="zip code" + ), + ), + migrations.AlterField( + model_name="domainrequest", + name="is_election_board", + field=models.BooleanField( + blank=True, + help_text="Is your organization an election office?", + null=True, + verbose_name="election office", + ), + ), + migrations.AlterField( + model_name="domainrequest", + name="state_territory", + field=models.CharField( + blank=True, + choices=[ + ("AL", "Alabama (AL)"), + ("AK", "Alaska (AK)"), + ("AS", "American Samoa (AS)"), + ("AZ", "Arizona (AZ)"), + ("AR", "Arkansas (AR)"), + ("CA", "California (CA)"), + ("CO", "Colorado (CO)"), + ("CT", "Connecticut (CT)"), + ("DE", "Delaware (DE)"), + ("DC", "District of Columbia (DC)"), + ("FL", "Florida (FL)"), + ("GA", "Georgia (GA)"), + ("GU", "Guam (GU)"), + ("HI", "Hawaii (HI)"), + ("ID", "Idaho (ID)"), + ("IL", "Illinois (IL)"), + ("IN", "Indiana (IN)"), + ("IA", "Iowa (IA)"), + ("KS", "Kansas (KS)"), + ("KY", "Kentucky (KY)"), + ("LA", "Louisiana (LA)"), + ("ME", "Maine (ME)"), + ("MD", "Maryland (MD)"), + ("MA", "Massachusetts (MA)"), + ("MI", "Michigan (MI)"), + ("MN", "Minnesota (MN)"), + ("MS", "Mississippi (MS)"), + ("MO", "Missouri (MO)"), + ("MT", "Montana (MT)"), + ("NE", "Nebraska (NE)"), + ("NV", "Nevada (NV)"), + ("NH", "New Hampshire (NH)"), + ("NJ", "New Jersey (NJ)"), + ("NM", "New Mexico (NM)"), + ("NY", "New York (NY)"), + ("NC", "North Carolina (NC)"), + ("ND", "North Dakota (ND)"), + ("MP", "Northern Mariana Islands (MP)"), + ("OH", "Ohio (OH)"), + ("OK", "Oklahoma (OK)"), + ("OR", "Oregon (OR)"), + ("PA", "Pennsylvania (PA)"), + ("PR", "Puerto Rico (PR)"), + ("RI", "Rhode Island (RI)"), + ("SC", "South Carolina (SC)"), + ("SD", "South Dakota (SD)"), + ("TN", "Tennessee (TN)"), + ("TX", "Texas (TX)"), + ("UM", "United States Minor Outlying Islands (UM)"), + ("UT", "Utah (UT)"), + ("VT", "Vermont (VT)"), + ("VI", "Virgin Islands (VI)"), + ("VA", "Virginia (VA)"), + ("WA", "Washington (WA)"), + ("WV", "West Virginia (WV)"), + ("WI", "Wisconsin (WI)"), + ("WY", "Wyoming (WY)"), + ("AA", "Armed Forces Americas (AA)"), + ("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"), + ("AP", "Armed Forces Pacific (AP)"), + ], + help_text="State, territory, or military post", + max_length=2, + null=True, + verbose_name="state / territory", + ), + ), + migrations.AlterField( + model_name="domainrequest", + name="submission_date", + field=models.DateField( + blank=True, default=None, help_text="Date submitted", null=True, verbose_name="submitted at" + ), + ), + migrations.AlterField( + model_name="domainrequest", + name="zipcode", + field=models.CharField( + blank=True, db_index=True, help_text="Zip code", max_length=10, null=True, verbose_name="zip code" + ), + ), + migrations.AlterField( + model_name="draftdomain", + name="name", + field=models.CharField( + default=None, help_text="Fully qualified domain name", max_length=253, verbose_name="requested domain" + ), + ), + migrations.AlterField( + model_name="host", + name="name", + field=models.CharField( + default=None, help_text="Fully qualified domain name", max_length=253, verbose_name="host name" + ), + ), + migrations.AlterField( + model_name="hostip", + name="address", + field=models.CharField( + default=None, + help_text="IP address", + max_length=46, + validators=[django.core.validators.validate_ipv46_address], + verbose_name="IP address", + ), + ), + migrations.AlterField( + model_name="transitiondomain", + name="domain_name", + field=models.CharField(blank=True, null=True, verbose_name="domain"), + ), + migrations.AlterField( + model_name="transitiondomain", + name="first_name", + field=models.CharField( + blank=True, db_index=True, help_text="First name / given name", null=True, verbose_name="first name" + ), + ), + migrations.AlterField( + model_name="transitiondomain", + name="processed", + field=models.BooleanField( + default=True, + help_text="Indicates whether this TransitionDomain was already processed", + verbose_name="processed", + ), + ), + migrations.AlterField( + model_name="transitiondomain", + name="state_territory", + field=models.CharField( + blank=True, + help_text="State, territory, or military post", + max_length=2, + null=True, + verbose_name="state / territory", + ), + ), + migrations.AlterField( + model_name="transitiondomain", + name="status", + field=models.CharField( + blank=True, + choices=[("ready", "Ready"), ("on hold", "On hold"), ("unknown", "Unknown")], + default="ready", + help_text="domain status during the transfer", + max_length=255, + verbose_name="status", + ), + ), + migrations.AlterField( + model_name="transitiondomain", + name="title", + field=models.CharField(blank=True, help_text="Title", null=True, verbose_name="title / role"), + ), + migrations.AlterField( + model_name="transitiondomain", + name="username", + field=models.CharField(help_text="Username - this will be an email address", verbose_name="username"), + ), + migrations.AlterField( + model_name="transitiondomain", + name="zipcode", + field=models.CharField( + blank=True, db_index=True, help_text="Zip code", max_length=10, null=True, verbose_name="zip code" + ), + ), + migrations.AlterField( + model_name="user", + name="status", + field=models.CharField( + blank=True, + choices=[("restricted", "restricted")], + default=None, + max_length=10, + null=True, + verbose_name="user status", + ), + ), + ] diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index bf35b0143..9deb22641 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -18,7 +18,7 @@ class Contact(TimeStampedModel): first_name = models.CharField( null=True, blank=True, - verbose_name="first name / given name", + verbose_name="first name", db_index=True, ) middle_name = models.CharField( @@ -28,13 +28,13 @@ class Contact(TimeStampedModel): last_name = models.CharField( null=True, blank=True, - verbose_name="last name / family name", + verbose_name="last name", db_index=True, ) title = models.CharField( null=True, blank=True, - verbose_name="title or role in your organization", + verbose_name="title / role", ) email = models.EmailField( null=True, diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 079fce3bc..12994ea0e 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -992,6 +992,7 @@ class Domain(TimeStampedModel, DomainHelper): blank=False, default=None, # prevent saving without a value unique=True, + verbose_name="domain", help_text="Fully qualified domain name", ) @@ -1000,6 +1001,7 @@ class Domain(TimeStampedModel, DomainHelper): choices=State.choices, default=State.UNKNOWN, protected=True, # cannot change state directly, particularly in Django admin + verbose_name="domain state", help_text="Very basic info about the lifecycle of this domain object", ) @@ -1017,12 +1019,14 @@ class Domain(TimeStampedModel, DomainHelper): deleted = DateField( null=True, editable=False, + verbose_name="deleted on", help_text="Deleted at date", ) first_ready = DateField( null=True, editable=False, + verbose_name="first ready on", help_text="The last time this domain moved into the READY state", ) diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py index b02385d84..ef2b2fbb9 100644 --- a/src/registrar/models/domain_information.py +++ b/src/registrar/models/domain_information.py @@ -62,6 +62,7 @@ class DomainInformation(TimeStampedModel): is_election_board = models.BooleanField( null=True, blank=True, + verbose_name="election office", help_text="Is your organization an election office?", ) @@ -108,6 +109,7 @@ class DomainInformation(TimeStampedModel): is_election_board = models.BooleanField( null=True, blank=True, + verbose_name="election office", help_text="Is your organization an election office?", ) @@ -121,13 +123,13 @@ class DomainInformation(TimeStampedModel): null=True, blank=True, help_text="Street address", - verbose_name="Street address", + verbose_name="address line 1", ) address_line2 = models.CharField( null=True, blank=True, help_text="Street address line 2 (optional)", - verbose_name="Street address line 2 (optional)", + verbose_name="address line 2", ) city = models.CharField( null=True, @@ -139,21 +141,22 @@ class DomainInformation(TimeStampedModel): choices=StateTerritoryChoices.choices, null=True, blank=True, + verbose_name="state / territory", help_text="State, territory, or military post", - verbose_name="State, territory, or military post", ) zipcode = models.CharField( max_length=10, null=True, blank=True, help_text="Zip code", + verbose_name="zip code", db_index=True, ) urbanization = models.CharField( null=True, blank=True, help_text="Urbanization (required for Puerto Rico only)", - verbose_name="Urbanization (required for Puerto Rico only)", + verbose_name="urbanization", ) about_your_organization = models.TextField( diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 8fb4b78b9..4abdf27c0 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -478,6 +478,7 @@ class DomainRequest(TimeStampedModel): is_election_board = models.BooleanField( null=True, blank=True, + verbose_name="election office", help_text="Is your organization an election office?", ) @@ -550,12 +551,14 @@ class DomainRequest(TimeStampedModel): choices=StateTerritoryChoices.choices, null=True, blank=True, + verbose_name="state / territory", help_text="State, territory, or military post", ) zipcode = models.CharField( max_length=10, null=True, blank=True, + verbose_name="zip code", help_text="Zip code", db_index=True, ) @@ -657,6 +660,7 @@ class DomainRequest(TimeStampedModel): null=True, blank=True, default=None, + verbose_name="submitted at", help_text="Date submitted", ) diff --git a/src/registrar/models/draft_domain.py b/src/registrar/models/draft_domain.py index fc70a18f3..16fb1de33 100644 --- a/src/registrar/models/draft_domain.py +++ b/src/registrar/models/draft_domain.py @@ -18,5 +18,6 @@ class DraftDomain(TimeStampedModel, DomainHelper): max_length=253, blank=False, default=None, # prevent saving without a value + verbose_name="requested domain", help_text="Fully qualified domain name", ) diff --git a/src/registrar/models/host.py b/src/registrar/models/host.py index 3b966832f..2fb4b980b 100644 --- a/src/registrar/models/host.py +++ b/src/registrar/models/host.py @@ -21,6 +21,7 @@ class Host(TimeStampedModel): blank=False, default=None, # prevent saving without a value unique=False, + verbose_name="host name", help_text="Fully qualified domain name", ) diff --git a/src/registrar/models/host_ip.py b/src/registrar/models/host_ip.py index 777d14430..216ad9eca 100644 --- a/src/registrar/models/host_ip.py +++ b/src/registrar/models/host_ip.py @@ -20,6 +20,7 @@ class HostIP(TimeStampedModel): blank=False, default=None, # prevent saving without a value validators=[validate_ipv46_address], + verbose_name="IP address", help_text="IP address", ) diff --git a/src/registrar/models/transition_domain.py b/src/registrar/models/transition_domain.py index eafbeda00..2dafd6da4 100644 --- a/src/registrar/models/transition_domain.py +++ b/src/registrar/models/transition_domain.py @@ -20,13 +20,13 @@ class TransitionDomain(TimeStampedModel): username = models.CharField( null=False, blank=False, - verbose_name="Username", + verbose_name="username", help_text="Username - this will be an email address", ) domain_name = models.CharField( null=True, blank=True, - verbose_name="Domain name", + verbose_name="domain", ) status = models.CharField( max_length=255, @@ -34,7 +34,7 @@ class TransitionDomain(TimeStampedModel): blank=True, default=StatusChoices.READY, choices=StatusChoices.choices, - verbose_name="Status", + verbose_name="status", help_text="domain status during the transfer", ) email_sent = models.BooleanField( @@ -46,7 +46,7 @@ class TransitionDomain(TimeStampedModel): processed = models.BooleanField( null=False, default=True, - verbose_name="Processed", + verbose_name="processed", help_text="Indicates whether this TransitionDomain was already processed", ) generic_org_type = models.CharField( @@ -83,8 +83,8 @@ class TransitionDomain(TimeStampedModel): first_name = models.CharField( null=True, blank=True, - help_text="First name", - verbose_name="first name / given name", + help_text="First name / given name", + verbose_name="first name", db_index=True, ) middle_name = models.CharField( @@ -100,6 +100,7 @@ class TransitionDomain(TimeStampedModel): title = models.CharField( null=True, blank=True, + verbose_name="title / role", help_text="Title", ) email = models.EmailField( @@ -126,12 +127,14 @@ class TransitionDomain(TimeStampedModel): max_length=2, null=True, blank=True, + verbose_name="state / territory", help_text="State, territory, or military post", ) zipcode = models.CharField( max_length=10, null=True, blank=True, + verbose_name="zip code", help_text="Zip code", db_index=True, ) diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 2688ef57f..cf027e70c 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -33,6 +33,7 @@ class User(AbstractUser): default=None, # Set the default value to None null=True, # Allow the field to be null blank=True, # Allow the field to be blank + verbose_name="user status", ) domains = models.ManyToManyField(