Files
fonrey/apps/property/models/core.py
ishenwei 79c3cf2924 feat(models): add Chinese verbose_name to all 74 models (Phase 4.0)
为所有 Django 模型添加 Meta.verbose_name 和 verbose_name_plural(中文表名),
覆盖 10 个 app 的全部 74 个业务模型。

Phase 4.0 范围:
- 仅 Meta 类级别中文名(用于 Django Admin、drf-spectacular OpenAPI title、错误信息)
- 字段级 verbose_name= 和 help_text= 留待 Phase 4.1(待 PM 补全 DATA_MODEL 后同步)

变更:
- 20 个 models 文件改动(每个模型 +2 行)
- 8 个 0002/0003 迁移文件(Meta options 变更)
- apps/tenant/migrations/0001_initial.py(之前漏生成的 tenant 模型迁移)

manage.py check: 0 issues。
2026-04-29 19:10:38 +08:00

351 lines
13 KiB
Python

from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField
from django.db import models
from core.enums import (
PropertyAttribute,
PropertyContactGender,
PropertyContactIdentity,
PropertyDecoration,
PropertyGrade,
PropertyHouseStatus,
PropertyOrientation,
PropertyOwnershipNature,
PropertyPaymentMethod,
PropertyShopLocation,
PropertyStatus,
PropertyTaxIncluded,
PropertyType,
PropertyViewingTime,
)
from core.models.base import SoftDeleteModel, TimeStampedModel, UUIDPrimaryKeyModel
class Property(SoftDeleteModel):
property_type = models.CharField(max_length=30, choices=PropertyType.choices)
status = models.CharField(
max_length=20,
choices=PropertyStatus.choices,
default=PropertyStatus.FOR_SALE,
)
attribute = models.CharField(
max_length=10,
choices=PropertyAttribute.choices,
default=PropertyAttribute.PUBLIC,
)
private_reason = models.TextField(blank=True, default="")
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.RESTRICT,
related_name="properties",
)
building = models.ForeignKey(
"fonrey_complex.Building",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="properties",
)
block_no = models.CharField(max_length=30, blank=True, default="")
unit_no = models.CharField(max_length=30, blank=True, default="")
room_no = models.CharField(max_length=30, blank=True, default="")
floor = models.SmallIntegerField()
total_floors = models.SmallIntegerField()
bedroom_count = models.SmallIntegerField(default=0)
living_room_count = models.SmallIntegerField(default=0)
bathroom_count = models.SmallIntegerField(default=0)
kitchen_count = models.SmallIntegerField(default=0)
balcony_count = models.SmallIntegerField(default=0)
area = models.DecimalField(max_digits=8, decimal_places=2)
inner_area = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
sale_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
sale_bottom_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
sale_record_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
rent_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
orientation = models.CharField(
max_length=15, blank=True, default="", choices=PropertyOrientation.choices
)
decoration = models.CharField(
max_length=10, blank=True, default="", choices=PropertyDecoration.choices
)
has_elevator = models.BooleanField(null=True, blank=True)
built_year = models.SmallIntegerField(null=True, blank=True)
usage_type = models.CharField(max_length=30, blank=True, default="")
usage_subtype = models.CharField(max_length=30, blank=True, default="")
shop_frontage = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)
shop_depth = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)
shop_height = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)
shop_location = models.CharField(
max_length=20, blank=True, default="", choices=PropertyShopLocation.choices
)
house_status = models.CharField(
max_length=20, blank=True, default="", choices=PropertyHouseStatus.choices
)
viewing_time = models.CharField(
max_length=20, blank=True, default="", choices=PropertyViewingTime.choices
)
grade = models.CharField(max_length=2, blank=True, default="", choices=PropertyGrade.choices)
ownership_years = models.CharField(max_length=30, blank=True, default="")
ownership_years_detail = models.CharField(max_length=20, blank=True, default="")
ownership_nature = models.CharField(
max_length=20, blank=True, default="", choices=PropertyOwnershipNature.choices
)
is_only_house = models.BooleanField(null=True, blank=True)
payment_method = models.CharField(
max_length=15, blank=True, default="", choices=PropertyPaymentMethod.choices
)
tax_included = models.CharField(
max_length=15, blank=True, default="", choices=PropertyTaxIncluded.choices
)
has_mortgage = models.BooleanField(null=True, blank=True)
has_loan = models.BooleanField(null=True, blank=True)
has_seal = models.BooleanField(null=True, blank=True)
has_restriction = models.BooleanField(null=True, blank=True)
original_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
sale_reason = models.TextField(blank=True, default="")
remarks = models.TextField(blank=True, default="")
first_recorder = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="first_recorded_properties",
)
number_holder = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="held_properties",
)
seller_agent = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="selling_properties",
)
buyer_agent = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="buying_properties",
)
source = models.CharField(max_length=50, blank=True, default="")
completeness_score = models.SmallIntegerField(default=0)
listed_at = models.DateTimeField(null=True, blank=True)
last_followed_at = models.DateTimeField(null=True, blank=True)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_properties",
)
updated_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="updated_properties",
)
search_vector = SearchVectorField(null=True, blank=True)
version = models.IntegerField(default=1)
class Meta:
db_table = "properties"
verbose_name = "房源"
verbose_name_plural = "房源"
constraints = [
models.CheckConstraint(
check=models.Q(floor__gt=0) & models.Q(floor__lte=models.F("total_floors")),
name="chk_property_floor",
),
]
indexes = [
GinIndex(fields=["search_vector"], name="idx_properties_search"),
models.Index(fields=["complex"], name="idx_properties_complex"),
models.Index(fields=["status"], name="idx_properties_status"),
models.Index(fields=["sale_price"], name="idx_properties_sale_price"),
models.Index(fields=["area"], name="idx_properties_area"),
models.Index(fields=["listed_at"], name="idx_properties_listed_at"),
models.Index(fields=["last_followed_at"], name="idx_properties_last_followed"),
models.Index(fields=["bedroom_count"], name="idx_properties_bedroom"),
models.Index(fields=["grade"], name="idx_properties_grade"),
models.Index(fields=["completeness_score"], name="idx_properties_completeness"),
models.Index(fields=["seller_agent"], name="idx_properties_seller_agent"),
models.Index(fields=["number_holder"], name="idx_properties_number_holder"),
models.Index(
fields=["status", "attribute", "complex", "sale_price"],
name="idx_properties_list_composite",
),
models.Index(
fields=["seller_agent", "status", "listed_at"],
name="idx_properties_my_properties",
),
]
class PropertyContact(SoftDeleteModel):
property = models.ForeignKey(
Property, on_delete=models.CASCADE, related_name="contacts"
)
name = models.CharField(max_length=50)
gender = models.CharField(
max_length=10, choices=PropertyContactGender.choices, default=PropertyContactGender.MALE
)
identity = models.CharField(
max_length=20,
choices=PropertyContactIdentity.choices,
default=PropertyContactIdentity.CONTACT,
)
phone_enc = models.BinaryField()
phone_hash = models.CharField(max_length=64)
phone2_enc = models.BinaryField(null=True, blank=True)
phone2_hash = models.CharField(max_length=64, blank=True, default="")
wechat = models.CharField(max_length=100, blank=True, default="")
qq = models.CharField(max_length=20, blank=True, default="")
remarks = models.TextField(blank=True, default="")
is_number_holder = models.BooleanField(default=False)
number_holder_approved_at = models.DateTimeField(null=True, blank=True)
sort_order = models.IntegerField(default=0)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_property_contacts",
)
updated_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="updated_property_contacts",
)
class Meta:
db_table = "property_contacts"
verbose_name = "房源联系人"
verbose_name_plural = "房源联系人"
indexes = [
models.Index(fields=["property"], name="idx_pc_property"),
models.Index(fields=["phone_hash"], name="idx_pc_phone_hash"),
models.Index(fields=["phone2_hash"], name="idx_pc_phone2_hash"),
]
class PropertyMarketing(UUIDPrimaryKeyModel):
property = models.OneToOneField(
Property, on_delete=models.CASCADE, related_name="marketing"
)
marketing_title = models.CharField(max_length=30, blank=True, default="")
core_selling_points = models.TextField(blank=True, default="")
owner_attitude = models.TextField(blank=True, default="")
layout_description = models.TextField(blank=True, default="")
complex_description = models.TextField(blank=True, default="")
ai_generated_points = models.BooleanField(default=False)
ai_generated_attitude = models.BooleanField(default=False)
updated_at = models.DateTimeField(auto_now=True)
updated_by = models.ForeignKey(
"org.Staff", null=True, blank=True, on_delete=models.SET_NULL
)
class Meta:
db_table = "property_marketing"
verbose_name = "房源营销信息"
verbose_name_plural = "房源营销信息"
class PropertyCertificate(UUIDPrimaryKeyModel):
property = models.OneToOneField(
Property, on_delete=models.CASCADE, related_name="certificate"
)
owner_name = models.CharField(max_length=100, blank=True, default="")
owner_id_number = models.CharField(max_length=50, blank=True, default="")
owner_cert_type = models.CharField(max_length=20, blank=True, default="")
property_location = models.CharField(max_length=500, blank=True, default="")
cert_status = models.CharField(max_length=30, blank=True, default="")
cert_no = models.CharField(max_length=100, blank=True, default="")
first_registered_at = models.DateField(null=True, blank=True)
ownership_nature = models.CharField(max_length=30, blank=True, default="")
land_nature = models.CharField(max_length=30, blank=True, default="")
updated_at = models.DateTimeField(auto_now=True)
updated_by = models.ForeignKey(
"org.Staff", null=True, blank=True, on_delete=models.SET_NULL
)
class Meta:
db_table = "property_certificates"
verbose_name = "房源产证"
verbose_name_plural = "房源产证"
class PropertyCompleteness(UUIDPrimaryKeyModel):
property = models.OneToOneField(
Property, on_delete=models.CASCADE, related_name="completeness"
)
score_core_info = models.SmallIntegerField(default=0)
score_attachment = models.SmallIntegerField(default=0)
score_survey = models.SmallIntegerField(default=0)
score_vr = models.SmallIntegerField(default=0)
score_key = models.SmallIntegerField(default=0)
score_commission = models.SmallIntegerField(default=0)
score_verification = models.SmallIntegerField(default=0)
score_follow_up = models.SmallIntegerField(default=0)
score_viewing = models.SmallIntegerField(default=0)
score_other = models.SmallIntegerField(default=0)
total_score = models.SmallIntegerField(default=0)
calculated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "property_completeness"
verbose_name = "房源完整度"
verbose_name_plural = "房源完整度"
class PropertyProtection(UUIDPrimaryKeyModel):
property = models.OneToOneField(
Property, on_delete=models.CASCADE, related_name="protection"
)
is_protected = models.BooleanField(default=False)
reason = models.TextField(blank=True, default="")
start_at = models.DateTimeField(null=True, blank=True)
end_at = models.DateTimeField(null=True, blank=True)
set_by = models.ForeignKey(
"org.Staff", null=True, blank=True, on_delete=models.SET_NULL
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "property_protections"
verbose_name = "房源保护期"
verbose_name_plural = "房源保护期"