We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Django Admin: Indicate whether `creator` has other domains
### Issue description

Problem: an analyst would like to know whether the ~~**submitter**~~ **creator** is already associated with other domains in the system. This is an important indicator as to whether this submitter is trustworthy and established.

Solution: add an indicator near the **creator** name or email address, which shows how many other domains they are associated with in our system. This will be enhanced with a list of other requests or domains in a future ticket.


### Acceptance criteria

- [ ] a labeled indicator near the creator name, shows how many "Ready" domains they are a domain manager for, based on the user domain roles.
- [ ] a labeled indicator near the creator name, shows how many domain requests they have in progress.
- [ ] Labels are in plain english, as shown in Additional context.


### Additional context
Approved domains: nn
Active Requests : nn      
Rejected or Ineligible: nn

active requests will not include requests in the following states:started, approved, withdrawn
approved domains will not include deleted domains

### Links to other issues

Blocked by #1852.
Related to: #1875  (1850 should be done first)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/registrar/models/contact.py`
Content:
```
1 from django.db import models
2 
3 from .utility.time_stamped_model import TimeStampedModel
4 
5 from phonenumber_field.modelfields import PhoneNumberField  # type: ignore
6 
7 
8 class Contact(TimeStampedModel):
9     """Contact information follows a similar pattern for each contact."""
10 
11     user = models.OneToOneField(
12         "registrar.User",
13         null=True,
14         blank=True,
15         on_delete=models.SET_NULL,
16     )
17 
18     first_name = models.CharField(
19         null=True,
20         blank=True,
21         verbose_name="first name / given name",
22         db_index=True,
23     )
24     middle_name = models.CharField(
25         null=True,
26         blank=True,
27     )
28     last_name = models.CharField(
29         null=True,
30         blank=True,
31         verbose_name="last name / family name",
32         db_index=True,
33     )
34     title = models.CharField(
35         null=True,
36         blank=True,
37         verbose_name="title or role in your organization",
38     )
39     email = models.EmailField(
40         null=True,
41         blank=True,
42         db_index=True,
43         max_length=320,
44     )
45     phone = PhoneNumberField(
46         null=True,
47         blank=True,
48         db_index=True,
49     )
50 
51     def _get_all_relations(self):
52         """Returns an array of all fields which are relations"""
53         return [f.name for f in self._meta.get_fields() if f.is_relation]
54 
55     def has_more_than_one_join(self, expected_relation):
56         """Helper for finding whether an object is joined more than once.
57         expected_relation is the one relation with one expected join"""
58         # all_relations is the list of all_relations (from contact) to be checked for existing joins
59         all_relations = self._get_all_relations()
60         return any(self._has_more_than_one_join_per_relation(rel, expected_relation) for rel in all_relations)
61 
62     def _has_more_than_one_join_per_relation(self, relation, expected_relation):
63         """Helper for finding whether an object is joined more than once."""
64         # threshold is the number of related objects that are acceptable
65         # when determining if related objects exist. threshold is 0 for most
66         # relationships. if the relationship is expected_relation, we know that
67         # there is already exactly 1 acceptable relationship (the one we are
68         # attempting to delete), so the threshold is 1
69         threshold = 1 if relation == expected_relation else 0
70 
71         # Raise a KeyError if rel is not a defined field on the db_obj model
72         # This will help catch any errors in relation passed.
73         if relation not in [field.name for field in self._meta.get_fields()]:
74             raise KeyError(f"{relation} is not a defined field on the {self._meta.model_name} model.")
75 
76         # if attr rel in db_obj is not None, then test if reference object(s) exist
77         if getattr(self, relation) is not None:
78             field = self._meta.get_field(relation)
79             if isinstance(field, models.OneToOneField):
80                 # if the rel field is a OneToOne field, then we have already
81                 # determined that the object exists (is not None)
82                 # so return True unless the relation being tested is the expected_relation
83                 is_not_expected_relation = relation != expected_relation
84                 return is_not_expected_relation
85             elif isinstance(field, models.ForeignObjectRel):
86                 # if the rel field is a ManyToOne or ManyToMany, then we need
87                 # to determine if the count of related objects is greater than
88                 # the threshold
89                 return getattr(self, relation).count() > threshold
90         return False
91 
92     def get_formatted_name(self):
93         """Returns the contact's name in Western order."""
94         names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]
95         return " ".join(names) if names else "Unknown"
96 
97     def save(self, *args, **kwargs):
98         # Call the parent class's save method to perform the actual save
99         super().save(*args, **kwargs)
100 
101         # Update the related User object's first_name and last_name
102         if self.user and (not self.user.first_name or not self.user.last_name):
103             self.user.first_name = self.first_name
104             self.user.last_name = self.last_name
105             self.user.save()
106 
107     def __str__(self):
108         if self.first_name or self.last_name:
109             return self.get_formatted_name()
110         elif self.email:
111             return self.email
112         elif self.pk:
113             return str(self.pk)
114         else:
115             return ""
116
```

Path: `src/registrar/models/user.py`
Content:
```
1 import logging
2 
3 from django.contrib.auth.models import AbstractUser
4 from django.db import models
5 
6 from registrar.models.user_domain_role import UserDomainRole
7 
8 from .domain_invitation import DomainInvitation
9 from .transition_domain import TransitionDomain
10 from .verified_by_staff import VerifiedByStaff
11 from .domain import Domain
12 
13 from phonenumber_field.modelfields import PhoneNumberField  # type: ignore
14 
15 
16 logger = logging.getLogger(__name__)
17 
18 
19 class User(AbstractUser):
20     """
21     A custom user model that performs identically to the default user model
22     but can be customized later.
23     """
24 
25     # #### Constants for choice fields ####
26     RESTRICTED = "restricted"
27     STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)
28 
29     status = models.CharField(
30         max_length=10,
31         choices=STATUS_CHOICES,
32         default=None,  # Set the default value to None
33         null=True,  # Allow the field to be null
34         blank=True,  # Allow the field to be blank
35     )
36 
37     domains = models.ManyToManyField(
38         "registrar.Domain",
39         through="registrar.UserDomainRole",
40         related_name="users",
41     )
42 
43     phone = PhoneNumberField(
44         null=True,
45         blank=True,
46         help_text="Phone",
47         db_index=True,
48     )
49 
50     def __str__(self):
51         # this info is pulled from Login.gov
52         if self.first_name or self.last_name:
53             return f"{self.first_name or ''} {self.last_name or ''} {self.email or ''}"
54         elif self.email:
55             return self.email
56         else:
57             return self.username
58 
59     def restrict_user(self):
60         self.status = self.RESTRICTED
61         self.save()
62 
63     def unrestrict_user(self):
64         self.status = None
65         self.save()
66 
67     def is_restricted(self):
68         return self.status == self.RESTRICTED
69 
70     @classmethod
71     def needs_identity_verification(cls, email, uuid):
72         """A method used by our oidc classes to test whether a user needs email/uuid verification
73         or the full identity PII verification"""
74 
75         # An existing user who is a domain manager of a domain (that is,
76         # they have an entry in UserDomainRole for their User)
77         try:
78             existing_user = cls.objects.get(username=uuid)
79             if existing_user and UserDomainRole.objects.filter(user=existing_user).exists():
80                 return False
81         except cls.DoesNotExist:
82             # Do nothing when the user is not found, as we're checking for existence.
83             pass
84         except Exception as err:
85             raise err
86 
87         # A new incoming user who is a domain manager for one of the domains
88         # that we inputted from Verisign (that is, their email address appears
89         # in the username field of a TransitionDomain)
90         if TransitionDomain.objects.filter(username=email).exists():
91             return False
92 
93         # New users flagged by Staff to bypass ial2
94         if VerifiedByStaff.objects.filter(email=email).exists():
95             return False
96 
97         # A new incoming user who is being invited to be a domain manager (that is,
98         # their email address is in DomainInvitation for an invitation that is not yet "retrieved").
99         invited = DomainInvitation.DomainInvitationStatus.INVITED
100         if DomainInvitation.objects.filter(email=email, status=invited).exists():
101             return False
102 
103         return True
104 
105     def check_domain_invitations_on_login(self):
106         """When a user first arrives on the site, we need to retrieve any domain
107         invitations that match their email address."""
108         for invitation in DomainInvitation.objects.filter(
109             email__iexact=self.email, status=DomainInvitation.DomainInvitationStatus.INVITED
110         ):
111             try:
112                 invitation.retrieve()
113                 invitation.save()
114             except RuntimeError:
115                 # retrieving should not fail because of a missing user, but
116                 # if it does fail, log the error so a new user can continue
117                 # logging in
118                 logger.warn("Failed to retrieve invitation %s", invitation, exc_info=True)
119 
120     def create_domain_and_invite(self, transition_domain: TransitionDomain):
121         transition_domain_name = transition_domain.domain_name
122         transition_domain_status = transition_domain.status
123         transition_domain_email = transition_domain.username
124 
125         # type safety check.  name should never be none
126         if transition_domain_name is not None:
127             new_domain = Domain(name=transition_domain_name, state=transition_domain_status)
128             new_domain.save()
129             # check that a domain invitation doesn't already
130             # exist for this e-mail / Domain pair
131             domain_email_already_in_domain_invites = DomainInvitation.objects.filter(
132                 email=transition_domain_email.lower(), domain=new_domain
133             ).exists()
134             if not domain_email_already_in_domain_invites:
135                 # Create new domain invitation
136                 new_domain_invitation = DomainInvitation(email=transition_domain_email.lower(), domain=new_domain)
137                 new_domain_invitation.save()
138 
139     def on_each_login(self):
140         """Callback each time the user is authenticated.
141 
142         When a user arrives on the site each time, we need to retrieve any domain
143         invitations that match their email address.
144 
145         We also need to check if they are logging in with the same e-mail
146         as a transition domain and update our domainInfo objects accordingly.
147         """
148 
149         self.check_domain_invitations_on_login()
150 
151     class Meta:
152         permissions = [
153             ("analyst_access_permission", "Analyst Access Permission"),
154             ("full_access_permission", "Full Access Permission"),
155         ]
156
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py
--- a/src/registrar/models/contact.py
+++ b/src/registrar/models/contact.py
@@ -94,6 +94,9 @@
         names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]
         return " ".join(names) if names else "Unknown"
 
+    def has_contact_info(self):
+        return bool(self.title or self.email or self.phone)
+
     def save(self, *args, **kwargs):
         # Call the parent class's save method to perform the actual save
         super().save(*args, **kwargs)
diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py
--- a/src/registrar/models/user.py
+++ b/src/registrar/models/user.py
@@ -9,6 +9,7 @@
 from .transition_domain import TransitionDomain
 from .verified_by_staff import VerifiedByStaff
 from .domain import Domain
+from .domain_request import DomainRequest
 
 from phonenumber_field.modelfields import PhoneNumberField  # type: ignore
 
@@ -67,6 +68,33 @@
     def is_restricted(self):
         return self.status == self.RESTRICTED
 
+    def get_approved_domains_count(self):
+        """Return count of approved domains"""
+        allowed_states = [Domain.State.UNKNOWN, Domain.State.DNS_NEEDED, Domain.State.READY, Domain.State.ON_HOLD]
+        approved_domains_count = self.domains.filter(state__in=allowed_states).count()
+        return approved_domains_count
+
+    def get_active_requests_count(self):
+        """Return count of active requests"""
+        allowed_states = [
+            DomainRequest.DomainRequestStatus.SUBMITTED,
+            DomainRequest.DomainRequestStatus.IN_REVIEW,
+            DomainRequest.DomainRequestStatus.ACTION_NEEDED,
+        ]
+        active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count()
+        return active_requests_count
+
+    def get_rejected_requests_count(self):
+        """Return count of rejected requests"""
+        return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.REJECTED).count()
+
+    def get_ineligible_requests_count(self):
+        """Return count of ineligible requests"""
+        return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.INELIGIBLE).count()
+
+    def has_contact_info(self):
+        return bool(self.contact.title or self.contact.email or self.contact.phone)
+
     @classmethod
     def needs_identity_verification(cls, email, uuid):
         """A method used by our oidc classes to test whether a user needs email/uuid verification

{"golden_diff": "diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py\n--- a/src/registrar/models/contact.py\n+++ b/src/registrar/models/contact.py\n@@ -94,6 +94,9 @@\n         names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]\n         return \" \".join(names) if names else \"Unknown\"\n \n+    def has_contact_info(self):\n+        return bool(self.title or self.email or self.phone)\n+\n     def save(self, *args, **kwargs):\n         # Call the parent class's save method to perform the actual save\n         super().save(*args, **kwargs)\ndiff --git a/src/registrar/models/user.py b/src/registrar/models/user.py\n--- a/src/registrar/models/user.py\n+++ b/src/registrar/models/user.py\n@@ -9,6 +9,7 @@\n from .transition_domain import TransitionDomain\n from .verified_by_staff import VerifiedByStaff\n from .domain import Domain\n+from .domain_request import DomainRequest\n \n from phonenumber_field.modelfields import PhoneNumberField  # type: ignore\n \n@@ -67,6 +68,33 @@\n     def is_restricted(self):\n         return self.status == self.RESTRICTED\n \n+    def get_approved_domains_count(self):\n+        \"\"\"Return count of approved domains\"\"\"\n+        allowed_states = [Domain.State.UNKNOWN, Domain.State.DNS_NEEDED, Domain.State.READY, Domain.State.ON_HOLD]\n+        approved_domains_count = self.domains.filter(state__in=allowed_states).count()\n+        return approved_domains_count\n+\n+    def get_active_requests_count(self):\n+        \"\"\"Return count of active requests\"\"\"\n+        allowed_states = [\n+            DomainRequest.DomainRequestStatus.SUBMITTED,\n+            DomainRequest.DomainRequestStatus.IN_REVIEW,\n+            DomainRequest.DomainRequestStatus.ACTION_NEEDED,\n+        ]\n+        active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count()\n+        return active_requests_count\n+\n+    def get_rejected_requests_count(self):\n+        \"\"\"Return count of rejected requests\"\"\"\n+        return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.REJECTED).count()\n+\n+    def get_ineligible_requests_count(self):\n+        \"\"\"Return count of ineligible requests\"\"\"\n+        return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.INELIGIBLE).count()\n+\n+    def has_contact_info(self):\n+        return bool(self.contact.title or self.contact.email or self.contact.phone)\n+\n     @classmethod\n     def needs_identity_verification(cls, email, uuid):\n         \"\"\"A method used by our oidc classes to test whether a user needs email/uuid verification\n", "issue": "Django Admin: Indicate whether `creator` has other domains\n### Issue description\n\nProblem: an analyst would like to know whether the ~~**submitter**~~ **creator** is already associated with other domains in the system. This is an important indicator as to whether this submitter is trustworthy and established.\n\nSolution: add an indicator near the **creator** name or email address, which shows how many other domains they are associated with in our system. This will be enhanced with a list of other requests or domains in a future ticket.\n\n\n### Acceptance criteria\n\n- [ ] a labeled indicator near the creator name, shows how many \"Ready\" domains they are a domain manager for, based on the user domain roles.\n- [ ] a labeled indicator near the creator name, shows how many domain requests they have in progress.\n- [ ] Labels are in plain english, as shown in Additional context.\n\n\n### Additional context\nApproved domains: nn\nActive Requests : nn      \nRejected or Ineligible: nn\n\nactive requests will not include requests in the following states:started, approved, withdrawn\napproved domains will not include deleted domains\n\n### Links to other issues\n\nBlocked by #1852.\nRelated to: #1875  (1850 should be done first)\n", "before_files": [{"content": "from django.db import models\n\nfrom .utility.time_stamped_model import TimeStampedModel\n\nfrom phonenumber_field.modelfields import PhoneNumberField  # type: ignore\n\n\nclass Contact(TimeStampedModel):\n    \"\"\"Contact information follows a similar pattern for each contact.\"\"\"\n\n    user = models.OneToOneField(\n        \"registrar.User\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n    )\n\n    first_name = models.CharField(\n        null=True,\n        blank=True,\n        verbose_name=\"first name / given name\",\n        db_index=True,\n    )\n    middle_name = models.CharField(\n        null=True,\n        blank=True,\n    )\n    last_name = models.CharField(\n        null=True,\n        blank=True,\n        verbose_name=\"last name / family name\",\n        db_index=True,\n    )\n    title = models.CharField(\n        null=True,\n        blank=True,\n        verbose_name=\"title or role in your organization\",\n    )\n    email = models.EmailField(\n        null=True,\n        blank=True,\n        db_index=True,\n        max_length=320,\n    )\n    phone = PhoneNumberField(\n        null=True,\n        blank=True,\n        db_index=True,\n    )\n\n    def _get_all_relations(self):\n        \"\"\"Returns an array of all fields which are relations\"\"\"\n        return [f.name for f in self._meta.get_fields() if f.is_relation]\n\n    def has_more_than_one_join(self, expected_relation):\n        \"\"\"Helper for finding whether an object is joined more than once.\n        expected_relation is the one relation with one expected join\"\"\"\n        # all_relations is the list of all_relations (from contact) to be checked for existing joins\n        all_relations = self._get_all_relations()\n        return any(self._has_more_than_one_join_per_relation(rel, expected_relation) for rel in all_relations)\n\n    def _has_more_than_one_join_per_relation(self, relation, expected_relation):\n        \"\"\"Helper for finding whether an object is joined more than once.\"\"\"\n        # threshold is the number of related objects that are acceptable\n        # when determining if related objects exist. threshold is 0 for most\n        # relationships. if the relationship is expected_relation, we know that\n        # there is already exactly 1 acceptable relationship (the one we are\n        # attempting to delete), so the threshold is 1\n        threshold = 1 if relation == expected_relation else 0\n\n        # Raise a KeyError if rel is not a defined field on the db_obj model\n        # This will help catch any errors in relation passed.\n        if relation not in [field.name for field in self._meta.get_fields()]:\n            raise KeyError(f\"{relation} is not a defined field on the {self._meta.model_name} model.\")\n\n        # if attr rel in db_obj is not None, then test if reference object(s) exist\n        if getattr(self, relation) is not None:\n            field = self._meta.get_field(relation)\n            if isinstance(field, models.OneToOneField):\n                # if the rel field is a OneToOne field, then we have already\n                # determined that the object exists (is not None)\n                # so return True unless the relation being tested is the expected_relation\n                is_not_expected_relation = relation != expected_relation\n                return is_not_expected_relation\n            elif isinstance(field, models.ForeignObjectRel):\n                # if the rel field is a ManyToOne or ManyToMany, then we need\n                # to determine if the count of related objects is greater than\n                # the threshold\n                return getattr(self, relation).count() > threshold\n        return False\n\n    def get_formatted_name(self):\n        \"\"\"Returns the contact's name in Western order.\"\"\"\n        names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]\n        return \" \".join(names) if names else \"Unknown\"\n\n    def save(self, *args, **kwargs):\n        # Call the parent class's save method to perform the actual save\n        super().save(*args, **kwargs)\n\n        # Update the related User object's first_name and last_name\n        if self.user and (not self.user.first_name or not self.user.last_name):\n            self.user.first_name = self.first_name\n            self.user.last_name = self.last_name\n            self.user.save()\n\n    def __str__(self):\n        if self.first_name or self.last_name:\n            return self.get_formatted_name()\n        elif self.email:\n            return self.email\n        elif self.pk:\n            return str(self.pk)\n        else:\n            return \"\"\n", "path": "src/registrar/models/contact.py"}, {"content": "import logging\n\nfrom django.contrib.auth.models import AbstractUser\nfrom django.db import models\n\nfrom registrar.models.user_domain_role import UserDomainRole\n\nfrom .domain_invitation import DomainInvitation\nfrom .transition_domain import TransitionDomain\nfrom .verified_by_staff import VerifiedByStaff\nfrom .domain import Domain\n\nfrom phonenumber_field.modelfields import PhoneNumberField  # type: ignore\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass User(AbstractUser):\n    \"\"\"\n    A custom user model that performs identically to the default user model\n    but can be customized later.\n    \"\"\"\n\n    # #### Constants for choice fields ####\n    RESTRICTED = \"restricted\"\n    STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)\n\n    status = models.CharField(\n        max_length=10,\n        choices=STATUS_CHOICES,\n        default=None,  # Set the default value to None\n        null=True,  # Allow the field to be null\n        blank=True,  # Allow the field to be blank\n    )\n\n    domains = models.ManyToManyField(\n        \"registrar.Domain\",\n        through=\"registrar.UserDomainRole\",\n        related_name=\"users\",\n    )\n\n    phone = PhoneNumberField(\n        null=True,\n        blank=True,\n        help_text=\"Phone\",\n        db_index=True,\n    )\n\n    def __str__(self):\n        # this info is pulled from Login.gov\n        if self.first_name or self.last_name:\n            return f\"{self.first_name or ''} {self.last_name or ''} {self.email or ''}\"\n        elif self.email:\n            return self.email\n        else:\n            return self.username\n\n    def restrict_user(self):\n        self.status = self.RESTRICTED\n        self.save()\n\n    def unrestrict_user(self):\n        self.status = None\n        self.save()\n\n    def is_restricted(self):\n        return self.status == self.RESTRICTED\n\n    @classmethod\n    def needs_identity_verification(cls, email, uuid):\n        \"\"\"A method used by our oidc classes to test whether a user needs email/uuid verification\n        or the full identity PII verification\"\"\"\n\n        # An existing user who is a domain manager of a domain (that is,\n        # they have an entry in UserDomainRole for their User)\n        try:\n            existing_user = cls.objects.get(username=uuid)\n            if existing_user and UserDomainRole.objects.filter(user=existing_user).exists():\n                return False\n        except cls.DoesNotExist:\n            # Do nothing when the user is not found, as we're checking for existence.\n            pass\n        except Exception as err:\n            raise err\n\n        # A new incoming user who is a domain manager for one of the domains\n        # that we inputted from Verisign (that is, their email address appears\n        # in the username field of a TransitionDomain)\n        if TransitionDomain.objects.filter(username=email).exists():\n            return False\n\n        # New users flagged by Staff to bypass ial2\n        if VerifiedByStaff.objects.filter(email=email).exists():\n            return False\n\n        # A new incoming user who is being invited to be a domain manager (that is,\n        # their email address is in DomainInvitation for an invitation that is not yet \"retrieved\").\n        invited = DomainInvitation.DomainInvitationStatus.INVITED\n        if DomainInvitation.objects.filter(email=email, status=invited).exists():\n            return False\n\n        return True\n\n    def check_domain_invitations_on_login(self):\n        \"\"\"When a user first arrives on the site, we need to retrieve any domain\n        invitations that match their email address.\"\"\"\n        for invitation in DomainInvitation.objects.filter(\n            email__iexact=self.email, status=DomainInvitation.DomainInvitationStatus.INVITED\n        ):\n            try:\n                invitation.retrieve()\n                invitation.save()\n            except RuntimeError:\n                # retrieving should not fail because of a missing user, but\n                # if it does fail, log the error so a new user can continue\n                # logging in\n                logger.warn(\"Failed to retrieve invitation %s\", invitation, exc_info=True)\n\n    def create_domain_and_invite(self, transition_domain: TransitionDomain):\n        transition_domain_name = transition_domain.domain_name\n        transition_domain_status = transition_domain.status\n        transition_domain_email = transition_domain.username\n\n        # type safety check.  name should never be none\n        if transition_domain_name is not None:\n            new_domain = Domain(name=transition_domain_name, state=transition_domain_status)\n            new_domain.save()\n            # check that a domain invitation doesn't already\n            # exist for this e-mail / Domain pair\n            domain_email_already_in_domain_invites = DomainInvitation.objects.filter(\n                email=transition_domain_email.lower(), domain=new_domain\n            ).exists()\n            if not domain_email_already_in_domain_invites:\n                # Create new domain invitation\n                new_domain_invitation = DomainInvitation(email=transition_domain_email.lower(), domain=new_domain)\n                new_domain_invitation.save()\n\n    def on_each_login(self):\n        \"\"\"Callback each time the user is authenticated.\n\n        When a user arrives on the site each time, we need to retrieve any domain\n        invitations that match their email address.\n\n        We also need to check if they are logging in with the same e-mail\n        as a transition domain and update our domainInfo objects accordingly.\n        \"\"\"\n\n        self.check_domain_invitations_on_login()\n\n    class Meta:\n        permissions = [\n            (\"analyst_access_permission\", \"Analyst Access Permission\"),\n            (\"full_access_permission\", \"Full Access Permission\"),\n        ]\n", "path": "src/registrar/models/user.py"}], "after_files": [{"content": "from django.db import models\n\nfrom .utility.time_stamped_model import TimeStampedModel\n\nfrom phonenumber_field.modelfields import PhoneNumberField  # type: ignore\n\n\nclass Contact(TimeStampedModel):\n    \"\"\"Contact information follows a similar pattern for each contact.\"\"\"\n\n    user = models.OneToOneField(\n        \"registrar.User\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n    )\n\n    first_name = models.CharField(\n        null=True,\n        blank=True,\n        verbose_name=\"first name / given name\",\n        db_index=True,\n    )\n    middle_name = models.CharField(\n        null=True,\n        blank=True,\n    )\n    last_name = models.CharField(\n        null=True,\n        blank=True,\n        verbose_name=\"last name / family name\",\n        db_index=True,\n    )\n    title = models.CharField(\n        null=True,\n        blank=True,\n        verbose_name=\"title or role in your organization\",\n    )\n    email = models.EmailField(\n        null=True,\n        blank=True,\n        db_index=True,\n        max_length=320,\n    )\n    phone = PhoneNumberField(\n        null=True,\n        blank=True,\n        db_index=True,\n    )\n\n    def _get_all_relations(self):\n        \"\"\"Returns an array of all fields which are relations\"\"\"\n        return [f.name for f in self._meta.get_fields() if f.is_relation]\n\n    def has_more_than_one_join(self, expected_relation):\n        \"\"\"Helper for finding whether an object is joined more than once.\n        expected_relation is the one relation with one expected join\"\"\"\n        # all_relations is the list of all_relations (from contact) to be checked for existing joins\n        all_relations = self._get_all_relations()\n        return any(self._has_more_than_one_join_per_relation(rel, expected_relation) for rel in all_relations)\n\n    def _has_more_than_one_join_per_relation(self, relation, expected_relation):\n        \"\"\"Helper for finding whether an object is joined more than once.\"\"\"\n        # threshold is the number of related objects that are acceptable\n        # when determining if related objects exist. threshold is 0 for most\n        # relationships. if the relationship is expected_relation, we know that\n        # there is already exactly 1 acceptable relationship (the one we are\n        # attempting to delete), so the threshold is 1\n        threshold = 1 if relation == expected_relation else 0\n\n        # Raise a KeyError if rel is not a defined field on the db_obj model\n        # This will help catch any errors in relation passed.\n        if relation not in [field.name for field in self._meta.get_fields()]:\n            raise KeyError(f\"{relation} is not a defined field on the {self._meta.model_name} model.\")\n\n        # if attr rel in db_obj is not None, then test if reference object(s) exist\n        if getattr(self, relation) is not None:\n            field = self._meta.get_field(relation)\n            if isinstance(field, models.OneToOneField):\n                # if the rel field is a OneToOne field, then we have already\n                # determined that the object exists (is not None)\n                # so return True unless the relation being tested is the expected_relation\n                is_not_expected_relation = relation != expected_relation\n                return is_not_expected_relation\n            elif isinstance(field, models.ForeignObjectRel):\n                # if the rel field is a ManyToOne or ManyToMany, then we need\n                # to determine if the count of related objects is greater than\n                # the threshold\n                return getattr(self, relation).count() > threshold\n        return False\n\n    def get_formatted_name(self):\n        \"\"\"Returns the contact's name in Western order.\"\"\"\n        names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]\n        return \" \".join(names) if names else \"Unknown\"\n\n    def has_contact_info(self):\n        return bool(self.title or self.email or self.phone)\n\n    def save(self, *args, **kwargs):\n        # Call the parent class's save method to perform the actual save\n        super().save(*args, **kwargs)\n\n        # Update the related User object's first_name and last_name\n        if self.user and (not self.user.first_name or not self.user.last_name):\n            self.user.first_name = self.first_name\n            self.user.last_name = self.last_name\n            self.user.save()\n\n    def __str__(self):\n        if self.first_name or self.last_name:\n            return self.get_formatted_name()\n        elif self.email:\n            return self.email\n        elif self.pk:\n            return str(self.pk)\n        else:\n            return \"\"\n", "path": "src/registrar/models/contact.py"}, {"content": "import logging\n\nfrom django.contrib.auth.models import AbstractUser\nfrom django.db import models\n\nfrom registrar.models.user_domain_role import UserDomainRole\n\nfrom .domain_invitation import DomainInvitation\nfrom .transition_domain import TransitionDomain\nfrom .verified_by_staff import VerifiedByStaff\nfrom .domain import Domain\nfrom .domain_request import DomainRequest\n\nfrom phonenumber_field.modelfields import PhoneNumberField  # type: ignore\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass User(AbstractUser):\n    \"\"\"\n    A custom user model that performs identically to the default user model\n    but can be customized later.\n    \"\"\"\n\n    # #### Constants for choice fields ####\n    RESTRICTED = \"restricted\"\n    STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)\n\n    status = models.CharField(\n        max_length=10,\n        choices=STATUS_CHOICES,\n        default=None,  # Set the default value to None\n        null=True,  # Allow the field to be null\n        blank=True,  # Allow the field to be blank\n    )\n\n    domains = models.ManyToManyField(\n        \"registrar.Domain\",\n        through=\"registrar.UserDomainRole\",\n        related_name=\"users\",\n    )\n\n    phone = PhoneNumberField(\n        null=True,\n        blank=True,\n        help_text=\"Phone\",\n        db_index=True,\n    )\n\n    def __str__(self):\n        # this info is pulled from Login.gov\n        if self.first_name or self.last_name:\n            return f\"{self.first_name or ''} {self.last_name or ''} {self.email or ''}\"\n        elif self.email:\n            return self.email\n        else:\n            return self.username\n\n    def restrict_user(self):\n        self.status = self.RESTRICTED\n        self.save()\n\n    def unrestrict_user(self):\n        self.status = None\n        self.save()\n\n    def is_restricted(self):\n        return self.status == self.RESTRICTED\n\n    def get_approved_domains_count(self):\n        \"\"\"Return count of approved domains\"\"\"\n        allowed_states = [Domain.State.UNKNOWN, Domain.State.DNS_NEEDED, Domain.State.READY, Domain.State.ON_HOLD]\n        approved_domains_count = self.domains.filter(state__in=allowed_states).count()\n        return approved_domains_count\n\n    def get_active_requests_count(self):\n        \"\"\"Return count of active requests\"\"\"\n        allowed_states = [\n            DomainRequest.DomainRequestStatus.SUBMITTED,\n            DomainRequest.DomainRequestStatus.IN_REVIEW,\n            DomainRequest.DomainRequestStatus.ACTION_NEEDED,\n        ]\n        active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count()\n        return active_requests_count\n\n    def get_rejected_requests_count(self):\n        \"\"\"Return count of rejected requests\"\"\"\n        return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.REJECTED).count()\n\n    def get_ineligible_requests_count(self):\n        \"\"\"Return count of ineligible requests\"\"\"\n        return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.INELIGIBLE).count()\n\n    def has_contact_info(self):\n        return bool(self.contact.title or self.contact.email or self.contact.phone)\n\n    @classmethod\n    def needs_identity_verification(cls, email, uuid):\n        \"\"\"A method used by our oidc classes to test whether a user needs email/uuid verification\n        or the full identity PII verification\"\"\"\n\n        # An existing user who is a domain manager of a domain (that is,\n        # they have an entry in UserDomainRole for their User)\n        try:\n            existing_user = cls.objects.get(username=uuid)\n            if existing_user and UserDomainRole.objects.filter(user=existing_user).exists():\n                return False\n        except cls.DoesNotExist:\n            # Do nothing when the user is not found, as we're checking for existence.\n            pass\n        except Exception as err:\n            raise err\n\n        # A new incoming user who is a domain manager for one of the domains\n        # that we inputted from Verisign (that is, their email address appears\n        # in the username field of a TransitionDomain)\n        if TransitionDomain.objects.filter(username=email).exists():\n            return False\n\n        # New users flagged by Staff to bypass ial2\n        if VerifiedByStaff.objects.filter(email=email).exists():\n            return False\n\n        # A new incoming user who is being invited to be a domain manager (that is,\n        # their email address is in DomainInvitation for an invitation that is not yet \"retrieved\").\n        invited = DomainInvitation.DomainInvitationStatus.INVITED\n        if DomainInvitation.objects.filter(email=email, status=invited).exists():\n            return False\n\n        return True\n\n    def check_domain_invitations_on_login(self):\n        \"\"\"When a user first arrives on the site, we need to retrieve any domain\n        invitations that match their email address.\"\"\"\n        for invitation in DomainInvitation.objects.filter(\n            email__iexact=self.email, status=DomainInvitation.DomainInvitationStatus.INVITED\n        ):\n            try:\n                invitation.retrieve()\n                invitation.save()\n            except RuntimeError:\n                # retrieving should not fail because of a missing user, but\n                # if it does fail, log the error so a new user can continue\n                # logging in\n                logger.warn(\"Failed to retrieve invitation %s\", invitation, exc_info=True)\n\n    def create_domain_and_invite(self, transition_domain: TransitionDomain):\n        transition_domain_name = transition_domain.domain_name\n        transition_domain_status = transition_domain.status\n        transition_domain_email = transition_domain.username\n\n        # type safety check.  name should never be none\n        if transition_domain_name is not None:\n            new_domain = Domain(name=transition_domain_name, state=transition_domain_status)\n            new_domain.save()\n            # check that a domain invitation doesn't already\n            # exist for this e-mail / Domain pair\n            domain_email_already_in_domain_invites = DomainInvitation.objects.filter(\n                email=transition_domain_email.lower(), domain=new_domain\n            ).exists()\n            if not domain_email_already_in_domain_invites:\n                # Create new domain invitation\n                new_domain_invitation = DomainInvitation(email=transition_domain_email.lower(), domain=new_domain)\n                new_domain_invitation.save()\n\n    def on_each_login(self):\n        \"\"\"Callback each time the user is authenticated.\n\n        When a user arrives on the site each time, we need to retrieve any domain\n        invitations that match their email address.\n\n        We also need to check if they are logging in with the same e-mail\n        as a transition domain and update our domainInfo objects accordingly.\n        \"\"\"\n\n        self.check_domain_invitations_on_login()\n\n    class Meta:\n        permissions = [\n            (\"analyst_access_permission\", \"Analyst Access Permission\"),\n            (\"full_access_permission\", \"Full Access Permission\"),\n        ]\n", "path": "src/registrar/models/user.py"}]}