class SMSMessage(UUIDModel, TimestampModel): phone = models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to="smsbot.Number") direction = TurnoutEnumField(MessageDirectionType) message = models.TextField(blank=True, null=True) status = TurnoutEnumField(SMSDeliveryStatus, null=True) status_time = models.DateTimeField(null=True) twilio_sid = models.TextField(null=True, db_index=True) kwargs = JSONField(null=True) blast = models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to="smsbot.Blast") class Meta(object): ordering = ["-created_at"] def __str__(self): return f"SMSMessage - {self.phone_id} {self.direction}" def delivery_status_webhook(self): if settings.PRIMARY_ORIGIN.startswith("https"): return f"{settings.PRIMARY_ORIGIN}/v1/smsbot/twilio-message-status/{self.uuid}/" else: # twilio rejects http webhooks return None
class Report(SubscriberModel, UUIDModel, TimestampModel): author = models.ForeignKey("accounts.User", null=True, on_delete=models.PROTECT) status = TurnoutEnumField(enums.ReportStatus, null=True, default=enums.ReportStatus.PENDING) type = TurnoutEnumField(enums.ReportType, null=True) result_item = models.ForeignKey("storage.StorageItem", null=True, on_delete=models.SET_NULL, blank=True)
class Lookup( ActionModel, SubscriberModel, TrackingModel, SearchableModel("email", "last_name", "first_name"), # type: ignore UUIDModel, TimestampModel, ): person = models.ForeignKey("people.Person", null=True, on_delete=models.PROTECT) first_name = models.TextField() last_name = models.TextField() state = models.ForeignKey("election.State", on_delete=models.PROTECT) registered = models.BooleanField(null=True) voter_status = TurnoutEnumField(enums.VoterStatus, null=True) too_many = models.BooleanField(null=True) last_updated = models.DateTimeField(null=True, blank=True) response = JSONField(null=True) alloy_response = JSONField(null=True) alloy_status = TurnoutEnumField(enums.VoterStatus, null=True) targetsmart_response = JSONField(null=True) targetsmart_status = TurnoutEnumField(enums.VoterStatus, null=True) date_of_birth = models.DateField(null=True, blank=True) unparsed_full_address = models.TextField(null=True, blank=True) address1 = models.TextField(null=True, blank=True) address2 = models.TextField(null=True, blank=True) city = models.TextField(null=True, blank=True) zipcode = models.TextField(null=True, blank=True, validators=[zip_validator]) age = models.IntegerField(null=True, blank=True) gender = TurnoutEnumField(enums.TargetSmartGender, null=True) phone = PhoneNumberField(null=True, blank=True, db_index=True) email = models.EmailField(null=True, blank=True) sms_opt_in = models.BooleanField(null=True, blank=True, default=None) class Meta(object): ordering = ["-created_at"] indexes = [ models.Index(fields=["created_at"]), ] def __str__(self): return f"Verification - {self.first_name} {self.last_name}, {self.state.pk}".strip( )
class Event(UUIDModel, TimestampModel): action = models.ForeignKey("action.Action", on_delete=models.PROTECT, db_index=True) event_type = TurnoutEnumField(enums.EventType, db_index=True) class Meta: ordering = ["-created_at"] indexes = [ models.Index(fields=["event_type", "created_at"]), ] def check_hooks(self, tool): from absentee.event_hooks import absentee_hooks from register.event_hooks import register_hooks from verifier.event_hooks import verifier_hooks # Tool-specific hooks if tool == enums.ToolName.VERIFY.value: hook = verifier_hooks.get(self.event_type) if hook: hook( self.action_id, self.uuid, ) if tool == enums.ToolName.REGISTER.value: hook = register_hooks.get(self.event_type) if hook: hook( self.action_id, self.uuid, ) if tool == enums.ToolName.ABSENTEE.value: hook = absentee_hooks.get(self.event_type) if hook: hook( self.action_id, self.uuid, )
class Subscription(UUIDModel, TimestampModel): subscriber = models.OneToOneField("multi_tenant.Client", on_delete=models.PROTECT) interest = models.ForeignKey(Interest, null=True, blank=True, on_delete=models.SET_NULL) expires = models.DateField(null=True, blank=True, db_index=True) plan = TurnoutEnumField( enums.SubscriberPlan, default=enums.SubscriberPlan.FREE, null=True, ) primary_contact_first_name = models.TextField(null=True) primary_contact_last_name = models.TextField(null=True) primary_contact_email = models.EmailField(null=True) primary_contact_phone = PhoneNumberField(blank=True, null=True) ein = models.TextField(null=True, blank=True) internal_notes = models.TextField(null=True, blank=True) # DEPRECATED product = models.ForeignKey(Product, null=True, blank=True, on_delete=models.SET_NULL) def __str__(self): return f"{self.subscriber} {self.expires}"
class SubscriberIntegrationProperty(UUIDModel, TimestampModel): subscriber = models.ForeignKey(Client, on_delete=models.CASCADE,) external_tool = TurnoutEnumField(enums.ExternalToolType, null=True) name = models.TextField(null=True) value = models.TextField(null=True) class Meta: ordering = ["-created_at"]
class SecureUploadItem(UUIDModel, TimestampModel): file = models.FileField(storage=HighValueStorage()) upload_type = TurnoutEnumField(enums.SecureUploadType) content_type = models.TextField() purged = models.DateTimeField(blank=True, null=True) class Meta: ordering = ["-created_at"]
class UpdateNotificationWebhook(UUIDModel, TimestampModel): name = models.TextField(null=True) type = TurnoutEnumField(enums.NotificationWebhookTypes, null=True) trigger_url = models.URLField(null=True, max_length=200) last_triggered = models.DateTimeField(null=True, blank=True) active = models.BooleanField(null=True, default=True) objects = UpdateNotificationWebhookManager() def __str__(self): return self.name
class Proxy(UUIDModel, TimestampModel): address = models.TextField(null=True) description = models.TextField(null=True) state = TurnoutEnumField(enums.ProxyStatus, null=True) failure_count = models.IntegerField(null=True) last_used = models.DateTimeField(null=True) class Meta: ordering = ["-created_at"] def __str__(self): return f"Proxy {self.uuid} - {self.address} {self.state}"
class LeoContactOverride(TimestampModel): region = models.OneToOneField("official.Region", on_delete=models.CASCADE, primary_key=True) submission_method = TurnoutEnumField(enums.SubmissionType, null=True) email = models.EmailField(null=True) phone = PhoneNumberField(null=True) fax = PhoneNumberField(null=True) notes = models.TextField(null=True, blank=True) class Meta: ordering = ["region__state__code", "region__name"]
class Link(UUIDModel, TimestampModel): action = models.ForeignKey("action.Action", null=True, on_delete=models.CASCADE) subscriber = models.ForeignKey("multi_tenant.Client", null=True, on_delete=models.CASCADE) external_tool = TurnoutEnumField(enums.ExternalToolType, null=True) external_id = models.TextField(null=True) class Meta: ordering = ["-created_at"]
class StateInformationFieldType(UUIDModel, TimestampModel): slug = models.SlugField("Name", max_length=50, unique=True) long_name = models.CharField("Long Name", max_length=200) help_text = models.TextField("Help Text", help_text="Markdown allowed", null=True, blank=True) field_format = TurnoutEnumField(enums.StateFieldFormats, null=True, default=enums.StateFieldFormats.MARKDOWN) class Meta(object): ordering = ["slug"] def __str__(self): return self.slug
class RegionEsignMethod(models.Model): region = models.OneToOneField( "official.Region", on_delete=models.DO_NOTHING, primary_key=True, related_name="esign", ) state = models.ForeignKey("election.State", on_delete=models.DO_NOTHING) has_override = models.BooleanField() submission_method = TurnoutEnumField(enums.SubmissionType) class Meta: managed = False ordering = ["state_id", "region__name"]
class Fax(UUIDModel, TimestampModel): token = models.UUIDField(default=uuid.uuid4) storage_item = models.ForeignKey( "storage.StorageItem", on_delete=models.PROTECT, ) status = TurnoutEnumField(enums.FaxStatus) status_message = models.TextField(null=True) status_timestamp = models.DateTimeField(null=True) on_complete_task = models.TextField(null=True) on_complete_task_arg = models.TextField(null=True) to = PhoneNumberField(null=True) class Meta: ordering = ["-created_at"] def validate_token(self, token): return token == str(self.token)
class Phone(UUIDModel, TimestampModel): phone_type = TurnoutEnumField(PhoneType, db_index=True) phone_number = PhoneNumberField()
class Interest( SearchableModel("organization_name", "first_name", "last_name", "email"), # type: ignore UUIDModel, TimestampModel, ): organization_name = models.TextField(null=True) website = models.URLField(null=True) first_name = models.TextField(null=True) last_name = models.TextField(null=True) email = models.EmailField(null=True) phone = PhoneNumberField(blank=True, null=True) plan = TurnoutEnumField( enums.SubscriberPlan, default=enums.SubscriberPlan.FREE, null=True, ) ein = models.TextField(null=True, blank=True) status = TurnoutEnumField( enums.SubscriptionInterestStatus, default=enums.SubscriptionInterestStatus.PENDING, null=True, ) reject_reason = models.TextField(null=True) sms_mode = TurnoutEnumField( enums.SubscriberSMSOption, default=enums.SubscriberSMSOption.BOX_UNCHECKED, null=True, ) # DEPRECATED nonprofit = models.BooleanField(null=True) product = models.ForeignKey(Product, null=True, on_delete=models.SET_NULL) class Meta: ordering = ["-created_at"] def __str__(self): return f"{self.organization_name} Interest Submission" @property def consumed(self): return self.status == enums.SubscriptionInterestStatus.SUBSCRIBED def consume(self, subscriber: "Client", initial_user_email: str, slug: str) -> None: from multi_tenant.models import SubscriberSlug self.status = enums.SubscriptionInterestStatus.SUBSCRIBED self.save() subscriber_slug = SubscriberSlug.objects.create(slug=slug, subscriber=subscriber) subscriber.default_slug = subscriber_slug subscriber.set_sms_mode(str(self.sms_mode)) subscriber.save() sub = Subscription.objects.create( subscriber=subscriber, product=self.product, plan=self.plan, primary_contact_first_name=self.first_name, primary_contact_last_name=self.last_name, primary_contact_email=self.email, primary_contact_phone=self.phone, ein=self.ein, interest=self, ) invite_user(initial_user_email, subscriber) send_organization_welcome_notification.delay(subscriber.pk, initial_user_email) sync_subscriber_to_actionnetwork.delay(subscriber.pk)
class StorageItem(SubscriberModel, UUIDModel, TimestampModel): token = SmallUUIDField(default=uuid_default()) app = TurnoutEnumField(enums.FileType) file = models.FileField(storage=HighValueDownloadStorage()) email = models.EmailField(blank=True, null=True) expires = models.DateTimeField(default=storage_expire_date_time) first_download = models.DateTimeField(blank=True, null=True) purged = models.DateTimeField(blank=True, null=True) preview_of = models.ForeignKey("self", on_delete=models.CASCADE, blank=True, null=True, db_index=True) class Meta: ordering = ["-created_at"] def get_absolute_url(self): return self.file.url def refresh_token(self): self.token = smalluuid.SmallUUID() self.expires = storage_expire_date_time() self.save(update_fields=["token", "expires"]) return self.token def validate_token(self, token): return (token == str(self.token)) and ( datetime.datetime.now(tz=datetime.timezone.utc) < self.expires) @property def download_url(self): path = reverse("storage:download", kwargs={"pk": self.pk}) return f"{settings.PRIMARY_ORIGIN}{path}?token={self.token}" @property def reset_url(self): return settings.FILE_TOKEN_RESET_URL.format(item_id=self.pk) @property def purged_url(self): return settings.FILE_TOKEN_PURGED_URL.format( item_id=self.pk, filetype=self.app, purge_days=settings.FILE_PURGE_DAYS) def track_download(self): if self.app == enums.FileType.REGISTRATION_FORM: try: registration = Registration.objects.get(result_item=self) except Registration.DoesNotExist: # we briefly linked to lob PDFs to download, but # stopped. some links are still in the wild. registration = Registration.objects.get(result_item_mail=self) registration.action.track_event(enums.EventType.DOWNLOAD) if self.app == enums.FileType.ABSENTEE_REQUEST_FORM: ballot_request = BallotRequest.objects.get(result_item=self) ballot_request.action.track_event(enums.EventType.DOWNLOAD) if not self.first_download: self.first_download = now() self.save(update_fields=["first_download"])
class Client(UUIDModel, TimestampModel): name = models.CharField(max_length=200) url = models.URLField(verbose_name="Homepage URL") email = models.EmailField( verbose_name="Default 'From' Email Address", max_length=254, null=True, default="*****@*****.**", ) privacy_policy = models.URLField( verbose_name="Privacy Policy URL", null=True, blank=True ) terms_of_service = models.URLField( verbose_name="Terms of Service URL", null=True, blank=True ) sms_enabled = models.BooleanField( verbose_name="SMS Program Enabled", default=True, null=True ) sms_checkbox = models.BooleanField( verbose_name="SMS Checkbox Enabled", default=True, null=True ) sms_checkbox_default = models.BooleanField( verbose_name="SMS Checkbox Checked By Default", default=False, null=True ) sms_disclaimer = models.TextField( verbose_name="Custom SMS Disclaimer", blank=True, null=True ) # In order to create this in the admin, we need blank=True default_slug = models.ForeignKey( "multi_tenant.SubscriberSlug", null=True, on_delete=models.PROTECT, blank=True ) status = TurnoutEnumField( enums.SubscriberStatus, default=enums.SubscriberStatus.ACTIVE, null=True ) # Passed to Civis to determine if subscriber's data should be synced to TMC sync_tmc = models.BooleanField( verbose_name="TMC Sync Enabled", default=False, null=True ) sync_bluelink = models.BooleanField( verbose_name="Bluelink Sync Enabled", default=False, null=True ) # Determines if we show our own donate asks when a user is interacting with # this client. is_first_party = models.BooleanField(default=False) has_api_access = models.BooleanField(default=False, null=True) class Meta: ordering = ["created_at"] verbose_name = "Subscriber" verbose_name_plural = "Subscribers" def __str__(self): return self.name @cached_property def slug(self): return self.default_slug.slug @property def full_email_address(self) -> str: if self.is_first_party: return f'"VoteAmerica" <*****@*****.**>' else: clean_name = self.name.replace('"', "'") return f'"{clean_name}" <{self.email}>' @property def transactional_from_email_address(self) -> str: if self.is_first_party: return self.full_email_address clean_name = self.name.replace('"', "'") return f'"{clean_name} via VoteAmerica" <{settings.DEFAULT_EMAIL_FROM}>' @property def transactional_from_name(self) -> str: if self.is_first_party: return "VoteAmerica" else: return f"{self.name} via VoteAmerica" @property def stats(self): refresh_obj = StatsRefresh.objects.all()[0] stats = { "last_updated": refresh_obj.last_run, "register": 0, "verify": 0, "absentee": 0, "locate": 0, } for stat in self.subscriberstats_set.all(): stats[stat.tool.value] = stat.count return stats @cached_property def disclaimer(self): return render_to_string(DISCLAIMER_TEMPLATE, {"subscriber": self}).replace( "\n", "" ) @cached_property def plan(self): if hasattr(self, "subscription"): return self.subscription.plan else: # System subscribers -- like VoteAmerica, and the dev Default one, # don't have subscriptions. return enums.SubscriberPlan.PREMIUM def plan_has_data_access(self): return self.plan != enums.SubscriberPlan.FREE def get_sms_mode(self): if not self.sms_enabled: return enums.SubscriberSMSOption.NONE if not self.sms_checkbox: return enums.SubscriberSMSOption.AUTO_OPT_IN if self.sms_checkbox_default: return enums.SubscriberSMSOption.BOX_CHECKED else: return enums.SubscriberSMSOption.BOX_UNCHECKED def set_sms_mode(self, opt): if opt == str(enums.SubscriberSMSOption.NONE): self.sms_enabled = False self.sms_checkbox = False self.sms_checkbox_default = False elif opt == str(enums.SubscriberSMSOption.AUTO_OPT_IN): self.sms_enabled = True self.sms_checkbox = False self.sms_checkbox_default = False elif opt == str(enums.SubscriberSMSOption.BOX_UNCHECKED): self.sms_enabled = True self.sms_checkbox = True self.sms_checkbox_default = False elif opt == str(enums.SubscriberSMSOption.BOX_CHECKED): self.sms_enabled = True self.sms_checkbox = True self.sms_checkbox_default = True
class Blast(TimestampModel, UUIDModel): description = models.CharField(max_length=100, null=True) content = models.TextField(null=True) sql = models.TextField(null=True) campaign = models.CharField(max_length=100, null=True) blast_type = TurnoutEnumField(BlastType, null=True) class Meta(object): ordering = ["-created_at"] def __str__(self): return f"Blast - {self.description} - {self.blast_type} ({self.uuid})" @tracer.wrap() def count(self): with connections["readonly"].cursor() as cursor: cursor.execute( f"SELECT count(*) FROM ({self.sql.format(blast_id=str(self.uuid),)}) a" ) return cursor.fetchall()[0][0] @tracer.wrap() def enqueue(self, test_phone: str = None): from .tasks import trigger_blast_sms, trigger_blast_mms_map if self.blast_type == BlastType.SMS and test_phone: trigger_blast_sms.delay(self.pk, test_phone, force_dup=True) return with connections["readonly"].cursor() as cursor: cursor.execute(self.sql.format(blast_id=str(self.uuid), )) columns = [col[0] for col in cursor.description] if test_phone: logger.info( f"Enqueueing test message for {self} to {test_phone}") else: logger.info( f"Enqueueing {cursor.rowcount} messages for {self}") for row in cursor.fetchall(): r = dict(zip(columns, row)) if self.blast_type == BlastType.SMS: trigger_blast_sms.delay(self.pk, r["phone"]) # type: ignore elif self.blast_type == BlastType.MMS_MAP: if test_phone: trigger_blast_mms_map.delay( self.pk, test_phone, r["map_type"], # type: ignore r["address_full"], # type: ignore force_dup=True, ) else: trigger_blast_mms_map.delay( self.pk, r["phone"], r["map_type"], r["address_full"] # type: ignore ) else: logger.error( f"unrecognized blast_type {self.blast_type} for {self}" ) if test_phone: break if not test_phone: logger.info( f"Done enqueueing {cursor.rowcount} messages for {self}")
class SubscriberStats(SubscriberModel, UUIDModel): tool = TurnoutEnumField(enums.ToolName) count = models.BigIntegerField() class Meta: unique_together = (("subscriber", "tool"), )
class BallotRequest( ActionModel, SubscriberModel, TrackingModel, SearchableModel("email", "last_name", "first_name"), # type: ignore UUIDModel, TimestampModel, ): first_name = models.TextField(null=True) middle_name = models.TextField(null=True, blank=True) last_name = models.TextField(null=True) suffix = models.TextField(null=True, blank=True) date_of_birth = models.DateField(null=True) email = models.EmailField(null=True) phone = PhoneNumberField(null=True, blank=True, db_index=True) address1 = models.TextField(null=True) address2 = models.TextField(null=True, blank=True) city = models.TextField(null=True) state = models.ForeignKey( "election.State", on_delete=models.PROTECT, related_name="absentee_primary", null=True, ) zipcode = models.TextField(null=True, validators=[zip_validator]) deliverable = models.BooleanField(null=True) mailing_address1 = models.TextField(null=True, blank=True) mailing_address2 = models.TextField(null=True, blank=True) mailing_city = models.TextField(null=True, blank=True) mailing_state = models.ForeignKey( "election.State", on_delete=models.PROTECT, related_name="absentee_mailing", null=True, ) mailing_zipcode = models.TextField(null=True, validators=[zip_validator], blank=True) mailing_deliverable = models.BooleanField(null=True) request_mailing_address1 = models.TextField(null=True, blank=True) request_mailing_address2 = models.TextField(null=True, blank=True) request_mailing_city = models.TextField(null=True, blank=True) request_mailing_state = models.ForeignKey( "election.State", on_delete=models.PROTECT, related_name="absentee_request_mailing", null=True, ) request_mailing_zipcode = models.TextField(null=True, validators=[zip_validator], blank=True) request_mailing_deliverable = models.BooleanField(null=True) us_citizen = models.BooleanField(null=True, default=False) sms_opt_in = models.BooleanField(null=True, default=False) state_fields = JSONField(null=True) status = TurnoutEnumField( enums.TurnoutActionStatus, default=enums.TurnoutActionStatus.PENDING, null=True, ) region = models.ForeignKey("official.Region", null=True, on_delete=models.SET_NULL) result_item = models.ForeignKey("storage.StorageItem", null=True, on_delete=models.SET_NULL) result_item_mail = models.ForeignKey( "storage.StorageItem", null=True, on_delete=models.SET_NULL, related_name="absentee_mail", ) signature = models.ForeignKey("storage.SecureUploadItem", null=True, on_delete=models.SET_NULL) submit_date = models.DateField(null=True) esign_method = TurnoutEnumField(enums.SubmissionType, null=True) referring_tool = TurnoutEnumField(enums.ToolName, null=True, blank=True) class Meta: ordering = ["-created_at"] indexes = [ models.Index(fields=["created_at"]), ] def __str__(self): return f"Absentee - {self.first_name} {self.last_name}, {self.state.pk}".strip( ) @property def full_name(self): if self.middle_name: return f"{self.first_name} {self.middle_name} {self.last_name}" return f"{self.first_name} {self.last_name}"
class Registration( ActionModel, SubscriberModel, TrackingModel, SearchableModel("email", "last_name", "first_name"), # type: ignore UUIDModel, TimestampModel, ): title = TurnoutEnumField(enums.PersonTitle, null=True) first_name = models.TextField(null=True) middle_name = models.TextField(null=True) last_name = models.TextField(null=True) suffix = models.TextField(null=True) date_of_birth = models.DateField(null=True) email = models.EmailField(null=True) phone = PhoneNumberField(null=True, db_index=True) address1 = models.TextField(null=True) address2 = models.TextField(null=True, blank=True) city = models.TextField(null=True) state = models.ForeignKey( "election.State", on_delete=models.PROTECT, related_name="registration_primary", null=True, ) zipcode = models.TextField(null=True, validators=[zip_validator]) deliverable = models.BooleanField(null=True) previous_title = TurnoutEnumField(enums.PersonTitle, null=True, blank=True) previous_first_name = models.TextField(null=True, blank=True) previous_middle_name = models.TextField(null=True, blank=True) previous_last_name = models.TextField(null=True, blank=True) previous_suffix = models.TextField(null=True, blank=True) previous_address1 = models.TextField(null=True, blank=True) previous_address2 = models.TextField(null=True, blank=True) previous_city = models.TextField(null=True, blank=True) previous_state = models.ForeignKey( "election.State", on_delete=models.PROTECT, related_name="registration_previous", null=True, ) previous_zipcode = models.TextField(null=True, validators=[zip_validator], blank=True) mailing_address1 = models.TextField(null=True, blank=True) mailing_address2 = models.TextField(null=True, blank=True) mailing_city = models.TextField(null=True, blank=True) mailing_state = models.ForeignKey( "election.State", on_delete=models.PROTECT, related_name="registration_mailing", null=True, ) mailing_zipcode = models.TextField(null=True, validators=[zip_validator], blank=True) mailing_deliverable = models.BooleanField(null=True) request_mailing_address1 = models.TextField(null=True, blank=True) request_mailing_address2 = models.TextField(null=True, blank=True) request_mailing_city = models.TextField(null=True, blank=True) request_mailing_state = models.ForeignKey( "election.State", on_delete=models.PROTECT, related_name="registration_request_mailing", null=True, ) request_mailing_zipcode = models.TextField(null=True, validators=[zip_validator], blank=True) request_mailing_deliverable = models.BooleanField(null=True) gender = TurnoutEnumField(enums.RegistrationGender, null=True) race_ethnicity = TurnoutEnumField(enums.RaceEthnicity, null=True) party = TurnoutEnumField(enums.PoliticalParties, null=True) us_citizen = models.BooleanField(null=True, default=False) sms_opt_in = models.BooleanField(null=True, blank=True, default=None) status = TurnoutEnumField( enums.TurnoutActionStatus, default=enums.TurnoutActionStatus.PENDING, null=True, ) result_item = models.ForeignKey("storage.StorageItem", null=True, on_delete=models.SET_NULL) result_item_mail = models.ForeignKey( "storage.StorageItem", null=True, on_delete=models.SET_NULL, related_name="registration_mail", ) referring_tool = TurnoutEnumField(enums.ToolName, null=True, blank=True) state_fields = JSONField(null=True, blank=True) state_api_result = JSONField(null=True, blank=True) custom_ovr_link = models.TextField(null=True, blank=True) matched_region = models.ForeignKey("official.Region", null=True, on_delete=models.SET_NULL, related_name="+") region = models.ForeignKey("official.Region", null=True, on_delete=models.SET_NULL, related_name="+") class Meta: ordering = ["-created_at"] indexes = [ models.Index(fields=["created_at"]), ] def __str__(self): return f"Registration - {self.first_name} {self.last_name}, {self.state.pk}".strip( ) @property def full_name(self): if self.middle_name: return f"{self.first_name} {self.middle_name} {self.last_name}" return f"{self.first_name} {self.last_name}"