Files
fonrey/apps/complex/models/complex.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

478 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField
from django.db import models
from core.enums import (
ComplexBuildingStructure,
ComplexBuildingType,
ComplexElectricityType,
ComplexPhotoCategory,
ComplexPropertyUsageType,
ComplexWaterType,
SchoolZoneType,
)
from core.models.base import SoftDeleteModel, TimeStampedModel, UUIDPrimaryKeyModel
class Complex(SoftDeleteModel):
name = models.CharField(max_length=200, help_text="标准楼盘名称,不可在编辑页直接修改")
district = models.ForeignKey(
"region.District",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="complexes",
)
address = models.CharField(max_length=500, blank=True, default="")
address_summary = models.CharField(max_length=100, blank=True, default="")
latitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
longitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
property_usage_types = ArrayField(
models.CharField(max_length=30, choices=ComplexPropertyUsageType.choices),
default=list,
blank=True,
)
building_structure = models.CharField(
max_length=30,
blank=True,
default="",
choices=ComplexBuildingStructure.choices,
)
building_type = models.CharField(
max_length=20,
blank=True,
default="",
choices=ComplexBuildingType.choices,
)
land_use_years = models.CharField(max_length=30, blank=True, default="")
built_year = models.SmallIntegerField(null=True, blank=True)
built_years = ArrayField(
models.SmallIntegerField(),
default=list,
blank=True,
)
ownership_category = ArrayField(
models.CharField(max_length=30),
default=list,
blank=True,
)
total_units = models.IntegerField(null=True, blank=True)
total_households = models.IntegerField(null=True, blank=True)
total_floor_area = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
plot_area = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
plot_ratio = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
green_rate = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
developer = models.CharField(max_length=200, blank=True, default="")
property_company = models.CharField(max_length=200, blank=True, default="")
property_fee = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
property_phone = models.CharField(max_length=30, blank=True, default="")
parking_total = models.IntegerField(null=True, blank=True)
parking_underground = models.IntegerField(null=True, blank=True)
parking_ratio = models.CharField(max_length=20, blank=True, default="")
water_type = models.CharField(
max_length=10,
blank=True,
default="",
choices=ComplexWaterType.choices,
)
electricity_type = models.CharField(
max_length=10,
blank=True,
default="",
choices=ComplexElectricityType.choices,
)
has_central_heating = models.BooleanField(null=True, blank=True)
has_gas = models.BooleanField(null=True, blank=True)
remarks = models.TextField(blank=True, default="")
lock_building = models.BooleanField(default=False)
lock_room = models.BooleanField(default=False)
lock_info = models.BooleanField(default=False)
lock_standard_room = models.BooleanField(default=False)
search_vector = SearchVectorField(null=True, blank=True)
is_active = models.BooleanField(default=True)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_complexes",
)
updated_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="updated_complexes",
)
version = models.IntegerField(default=1, help_text="乐观锁版本号UPDATE 时 +1")
business_areas = models.ManyToManyField(
"region.BusinessArea",
through="fonrey_complex.ComplexBusinessArea",
related_name="complexes",
)
schools = models.ManyToManyField(
"region.School",
through="fonrey_complex.ComplexSchool",
related_name="complexes",
)
metro_stations = models.ManyToManyField(
"region.MetroStation",
through="fonrey_complex.ComplexMetroStation",
related_name="complexes",
)
class Meta:
db_table = "complexes"
verbose_name = "楼盘"
verbose_name_plural = "楼盘"
indexes = [
models.Index(
fields=["district"],
name="idx_complexes_district",
condition=models.Q(deleted_at__isnull=True),
),
GinIndex(fields=["search_vector"], name="idx_complexes_search"),
models.Index(
fields=["latitude", "longitude"],
name="idx_complexes_geo",
condition=models.Q(deleted_at__isnull=True, latitude__isnull=False),
),
models.Index(
fields=["is_active"],
name="idx_complexes_active",
condition=models.Q(deleted_at__isnull=True),
),
]
ordering = ["name"]
def __str__(self) -> str:
return self.name
class ComplexAlias(UUIDPrimaryKeyModel):
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.CASCADE,
related_name="aliases",
)
alias = models.CharField(max_length=200)
is_system = models.BooleanField(default=False, help_text="TRUE=系统/标准别名(只读)")
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_complex_aliases",
)
class Meta:
db_table = "complex_aliases"
verbose_name = "楼盘别名"
verbose_name_plural = "楼盘别名"
indexes = [
models.Index(fields=["complex"], name="idx_complex_aliases_complex"),
]
ordering = ["complex_id", "alias"]
def __str__(self) -> str:
return self.alias
class ComplexBusinessArea(models.Model):
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.CASCADE,
related_name="complex_business_areas",
)
business_area = models.ForeignKey(
"region.BusinessArea",
on_delete=models.CASCADE,
related_name="complex_links",
)
is_primary = models.BooleanField(default=False)
class Meta:
db_table = "complex_business_areas"
verbose_name = "楼盘商圈关联"
verbose_name_plural = "楼盘商圈关联"
constraints = [
models.UniqueConstraint(
fields=["complex", "business_area"],
name="pk_complex_business_areas",
),
models.UniqueConstraint(
fields=["complex"],
condition=models.Q(is_primary=True),
name="uq_complex_biz_area_primary",
),
]
class ComplexSchool(models.Model):
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.CASCADE,
related_name="complex_schools",
)
school = models.ForeignKey(
"region.School",
on_delete=models.CASCADE,
related_name="complex_links",
)
zone_type = models.CharField(
max_length=30,
blank=True,
default="",
choices=SchoolZoneType.choices,
)
class Meta:
db_table = "complex_schools"
verbose_name = "楼盘学校关联"
verbose_name_plural = "楼盘学校关联"
constraints = [
models.UniqueConstraint(
fields=["complex", "school"],
name="pk_complex_schools",
),
]
indexes = [
models.Index(fields=["school"], name="idx_complex_schools_school"),
]
class ComplexMetroStation(models.Model):
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.CASCADE,
related_name="complex_metro_stations",
)
station = models.ForeignKey(
"region.MetroStation",
on_delete=models.CASCADE,
related_name="complex_links",
)
distance_meters = models.IntegerField(null=True, blank=True)
class Meta:
db_table = "complex_metro_stations"
verbose_name = "楼盘地铁站关联"
verbose_name_plural = "楼盘地铁站关联"
constraints = [
models.UniqueConstraint(
fields=["complex", "station"],
name="pk_complex_metro_stations",
),
]
indexes = [
models.Index(fields=["complex"], name="idx_complex_metro_complex"),
models.Index(fields=["station"], name="idx_complex_metro_station"),
]
class Building(TimeStampedModel):
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.CASCADE,
related_name="buildings",
)
name = models.CharField(max_length=50, help_text="楼栋名如「1号楼」「A栋2单元」")
is_standard = models.BooleanField(default=False, help_text="TRUE=标准结构(经运营核准)")
property_usage_type = models.CharField(
max_length=30,
blank=True,
default="",
choices=ComplexPropertyUsageType.choices,
)
built_year = models.SmallIntegerField(null=True, blank=True)
total_floors = models.SmallIntegerField(null=True, blank=True)
land_use_years = models.CharField(max_length=30, blank=True, default="")
has_elevator = models.BooleanField(null=True, blank=True)
school = models.ForeignKey(
"region.School",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="buildings",
)
is_active = models.BooleanField(default=True)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_buildings",
)
class Meta:
db_table = "buildings"
verbose_name = "楼栋"
verbose_name_plural = "楼栋"
indexes = [
models.Index(
fields=["complex"],
name="idx_buildings_complex",
condition=models.Q(is_active=True),
),
]
constraints = [
models.UniqueConstraint(
fields=["complex", "name"],
condition=models.Q(is_active=True),
name="uq_buildings_complex_name",
),
]
ordering = ["complex_id", "name"]
def __str__(self) -> str:
return self.name
class RoomUnit(TimeStampedModel):
building = models.ForeignKey(
"fonrey_complex.Building",
on_delete=models.CASCADE,
related_name="room_units",
)
floor = models.SmallIntegerField(help_text="楼层(实际层数,地下为负数)")
floor_name = models.CharField(max_length=20, blank=True, default="")
room_no = models.CharField(max_length=30)
display_no = models.CharField(max_length=50, blank=True, default="")
is_standard = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
class Meta:
db_table = "room_units"
verbose_name = "房号单元"
verbose_name_plural = "房号单元"
indexes = [
models.Index(
fields=["building"],
name="idx_room_units_building",
condition=models.Q(is_active=True),
),
]
constraints = [
models.UniqueConstraint(
fields=["building", "floor", "room_no"],
condition=models.Q(is_active=True),
name="uq_room_units_unique",
),
]
ordering = ["building_id", "-floor", "room_no"]
def __str__(self) -> str:
return self.display_no or f"{self.floor}/{self.room_no}"
class ComplexPhoto(UUIDPrimaryKeyModel):
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.CASCADE,
related_name="photos",
)
category = models.CharField(max_length=20, choices=ComplexPhotoCategory.choices)
file_key = models.TextField()
thumbnail_key = models.TextField(blank=True, default="")
file_name = models.CharField(max_length=255, blank=True, default="")
file_size = models.IntegerField(null=True, blank=True, help_text="bytes")
width = models.IntegerField(null=True, blank=True)
height = models.IntegerField(null=True, blank=True)
is_cover = models.BooleanField(default=False)
sort_order = models.SmallIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_complex_photos",
)
class Meta:
db_table = "complex_photos"
verbose_name = "楼盘照片"
verbose_name_plural = "楼盘照片"
indexes = [
models.Index(fields=["complex"], name="idx_complex_photos_complex"),
models.Index(fields=["complex", "category"], name="idx_complex_photos_category"),
]
constraints = [
models.UniqueConstraint(
fields=["complex"],
condition=models.Q(is_cover=True),
name="uq_complex_photos_cover",
),
]
ordering = ["complex_id", "sort_order"]
class ComplexAttachment(UUIDPrimaryKeyModel):
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.CASCADE,
related_name="attachments",
)
file_key = models.TextField()
file_name = models.CharField(max_length=255)
file_size = models.IntegerField(null=True, blank=True)
file_type = models.CharField(max_length=50, blank=True, default="", help_text="MIME type")
sort_order = models.SmallIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_complex_attachments",
)
class Meta:
db_table = "complex_attachments"
verbose_name = "楼盘附件"
verbose_name_plural = "楼盘附件"
ordering = ["complex_id", "sort_order"]
class ComplexPriceTrend(UUIDPrimaryKeyModel):
complex = models.ForeignKey(
"fonrey_complex.Complex",
on_delete=models.CASCADE,
related_name="price_trends",
)
record_month = models.DateField(help_text="月份统一存为该月1日")
avg_sale_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
avg_unit_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
transaction_count = models.IntegerField(null=True, blank=True)
listing_count = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "complex_price_trends"
verbose_name = "楼盘价格走势"
verbose_name_plural = "楼盘价格走势"
constraints = [
models.UniqueConstraint(
fields=["complex", "record_month"],
name="uq_complex_price_trend_month",
),
]
indexes = [
models.Index(
fields=["complex", "-record_month"],
name="idx_cpx_price_trend_complex",
),
]
ordering = ["complex_id", "-record_month"]