This commit is contained in:
2026-04-03 15:15:41 +03:30
parent bd0d04256c
commit e2728871ee
36 changed files with 1071 additions and 222 deletions
@@ -2,9 +2,10 @@ from django.db import migrations
FARM_TYPES = {
"زراعی": ["گندم", "ذرت"],
"درختی": ["سیب", "پسته"],
"زراعی": ["گندم", "ذرت", "جو", "کلزا", "پنبه"],
"درختی": ["سیب", "پسته", "انگور", "انار"],
"غرقابی": ["برنج"],
"گلخانه ای": ["گوجه فرنگی", "خیار", "فلفل دلمه ای"],
}
@@ -0,0 +1,31 @@
# Generated by Django 5.2.12 on 2026-03-20 00:30
import uuid
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("sensor_catalog", "0002_sensorcatalog_supported_power_sources"),
("farm_hub", "0002_seed_default_catalog"),
]
operations = [
migrations.AddField(
model_name="farmsensor",
name="physical_device_uuid",
field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
),
migrations.AddField(
model_name="farmsensor",
name="sensor_catalog",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="farm_sensors",
to="sensor_catalog.sensorcatalog",
),
),
]
@@ -0,0 +1,33 @@
# Generated by Django 5.2.12 on 2026-03-20 01:30
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("crop_zoning", "0004_croparea_farm"),
("farm_hub", "0003_farmsensor_catalog_and_physical_device"),
]
operations = [
migrations.RemoveField(
model_name="farmhub",
name="customization",
),
migrations.RemoveField(
model_name="farmsensor",
name="customization",
),
migrations.AddField(
model_name="farmhub",
name="current_crop_area",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="current_for_farms",
to="crop_zoning.croparea",
),
),
]
@@ -0,0 +1,163 @@
import json
from django.db import migrations, models
DEFAULT_FARM_TYPE_NAME = "زراعی"
def _table_exists(schema_editor, table_name):
with schema_editor.connection.cursor() as cursor:
existing_tables = set(schema_editor.connection.introspection.table_names(cursor))
return table_name in existing_tables
def _deserialize_json(value):
if value in (None, "", b""):
return {}
if isinstance(value, (dict, list)):
return value
if isinstance(value, bytes):
value = value.decode("utf-8")
try:
return json.loads(value)
except (TypeError, ValueError):
return {}
def migrate_plant_rows_to_products(apps, schema_editor):
if not _table_exists(schema_editor, "plant_plant"):
return
FarmType = apps.get_model("farm_hub", "FarmType")
Product = apps.get_model("farm_hub", "Product")
farm_type, _ = FarmType.objects.get_or_create(name=DEFAULT_FARM_TYPE_NAME)
with schema_editor.connection.cursor() as cursor:
cursor.execute(
"""
SELECT
name,
light,
watering,
soil,
temperature,
planting_season,
harvest_time,
spacing,
fertilizer,
health_profile,
irrigation_profile,
growth_profile,
created_at,
updated_at
FROM plant_plant
"""
)
columns = [column[0] for column in cursor.description]
rows = [dict(zip(columns, row)) for row in cursor.fetchall()]
for row in rows:
Product.objects.update_or_create(
farm_type=farm_type,
name=row["name"],
defaults={
"light": row["light"] or "",
"watering": row["watering"] or "",
"soil": row["soil"] or "",
"temperature": row["temperature"] or "",
"planting_season": row["planting_season"] or "",
"harvest_time": row["harvest_time"] or "",
"spacing": row["spacing"] or "",
"fertilizer": row["fertilizer"] or "",
"health_profile": _deserialize_json(row["health_profile"]),
"irrigation_profile": _deserialize_json(row["irrigation_profile"]),
"growth_profile": _deserialize_json(row["growth_profile"]),
"created_at": row["created_at"],
"updated_at": row["updated_at"],
},
)
def drop_legacy_plant_table(apps, schema_editor):
if _table_exists(schema_editor, "plant_plant"):
schema_editor.execute("DROP TABLE plant_plant")
class Migration(migrations.Migration):
dependencies = [
("farm_hub", "0004_remove_customization_add_current_crop_area"),
]
operations = [
migrations.AddField(
model_name="product",
name="fertilizer",
field=models.CharField(blank=True, default="", help_text="کود مناسب", max_length=255),
),
migrations.AddField(
model_name="product",
name="growth_profile",
field=models.JSONField(
blank=True,
default=dict,
help_text='پروفایل رشد محصول برای مدل GDD. {"base_temperature": 10, "required_gdd_for_maturity": 1200, "stage_thresholds": {"flowering": 500, "fruiting": 850}, "current_cumulative_gdd": 320}',
),
),
migrations.AddField(
model_name="product",
name="harvest_time",
field=models.CharField(blank=True, default="", help_text="زمان برداشت", max_length=255),
),
migrations.AddField(
model_name="product",
name="health_profile",
field=models.JSONField(
blank=True,
default=dict,
help_text='پروفایل سلامت محصول برای KPIها. ساختار نمونه: {"moisture": {"ideal_value": 65, "min_range": 45, "max_range": 75, "weight": 0.4}}',
),
),
migrations.AddField(
model_name="product",
name="irrigation_profile",
field=models.JSONField(
blank=True,
default=dict,
help_text='پروفایل آبیاری محصول برای محاسبات ETc. {"kc_initial": 0.6, "kc_mid": 1.15, "kc_end": 0.8, "growth_stage_duration": {"initial": 20, "mid": 30, "late": 25}}',
),
),
migrations.AddField(
model_name="product",
name="light",
field=models.CharField(blank=True, default="", help_text="نور مورد نیاز", max_length=255),
),
migrations.AddField(
model_name="product",
name="planting_season",
field=models.CharField(blank=True, default="", help_text="فصل کاشت", max_length=255),
),
migrations.AddField(
model_name="product",
name="soil",
field=models.CharField(blank=True, default="", help_text="خاک مناسب", max_length=255),
),
migrations.AddField(
model_name="product",
name="spacing",
field=models.CharField(blank=True, default="", help_text="فاصله کاشت", max_length=255),
),
migrations.AddField(
model_name="product",
name="temperature",
field=models.CharField(blank=True, default="", help_text="دمای مناسب", max_length=255),
),
migrations.AddField(
model_name="product",
name="watering",
field=models.CharField(blank=True, default="", help_text="آبیاری", max_length=255),
),
migrations.RunPython(migrate_plant_rows_to_products, migrations.RunPython.noop),
migrations.RunPython(drop_legacy_plant_table, migrations.RunPython.noop),
]
@@ -0,0 +1,55 @@
from django.db import migrations
CATALOG_SEED_DATA = {
"زراعی": [
{"name": "گندم", "planting_season": "پاییز", "harvest_time": "اواخر بهار", "soil": "لومی"},
{"name": "ذرت", "planting_season": "بهار", "harvest_time": "تابستان", "soil": "لومی شنی"},
{"name": "جو", "planting_season": "پاییز", "harvest_time": "اواخر بهار", "soil": "لومی"},
{"name": "کلزا", "planting_season": "پاییز", "harvest_time": "بهار", "soil": "لومی رسی"},
{"name": "پنبه", "planting_season": "بهار", "harvest_time": "پاییز", "soil": "لومی"},
],
"درختی": [
{"name": "سیب", "planting_season": "زمستان", "harvest_time": "پاییز", "soil": "لومی"},
{"name": "پسته", "planting_season": "زمستان", "harvest_time": "اواخر تابستان", "soil": "شنی لومی"},
{"name": "انگور", "planting_season": "اواخر زمستان", "harvest_time": "تابستان", "soil": "لومی"},
{"name": "انار", "planting_season": "اواخر زمستان", "harvest_time": "پاییز", "soil": "لومی شنی"},
],
"غرقابی": [
{"name": "برنج", "planting_season": "بهار", "harvest_time": "اواخر تابستان", "soil": "رسی"},
],
"گلخانه ای": [
{"name": "گوجه فرنگی", "planting_season": "چهار فصل", "harvest_time": "چند مرحله ای", "soil": "کوکوپیت"},
{"name": "خیار", "planting_season": "چهار فصل", "harvest_time": "چند مرحله ای", "soil": "پرلیت"},
{"name": "فلفل دلمه ای", "planting_season": "چهار فصل", "harvest_time": "چند مرحله ای", "soil": "بستر هیدروپونیک"},
],
}
def seed_expanded_catalog(apps, schema_editor):
FarmType = apps.get_model("farm_hub", "FarmType")
Product = apps.get_model("farm_hub", "Product")
for farm_type_name, products in CATALOG_SEED_DATA.items():
farm_type, _ = FarmType.objects.get_or_create(name=farm_type_name)
for product_data in products:
Product.objects.update_or_create(
farm_type=farm_type,
name=product_data["name"],
defaults={key: value for key, value in product_data.items() if key != "name"},
)
def unseed_expanded_catalog(apps, schema_editor):
FarmType = apps.get_model("farm_hub", "FarmType")
FarmType.objects.filter(name__in=CATALOG_SEED_DATA.keys()).delete()
class Migration(migrations.Migration):
dependencies = [
("farm_hub", "0005_product_profiles_and_plant_migration"),
]
operations = [
migrations.RunPython(seed_expanded_catalog, unseed_expanded_catalog),
]