From 530e31f9b549506f84b34d785993b06fcbf91e9b Mon Sep 17 00:00:00 2001 From: Akira Date: Sun, 15 Feb 2026 11:47:15 +0900 Subject: [PATCH] =?UTF-8?q?Day=203=20=E3=81=AE=E3=83=87=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=83=A2=E3=83=87=E3=83=AB=E5=AE=9F=E8=A3=85=E3=81=8C=E5=AE=8C?= =?UTF-8?q?=E4=BA=86=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82=20?= =?UTF-8?q?=E5=AE=8C=E4=BA=86=E5=86=85=E5=AE=B9=20models.py=20fields/model?= =?UTF-8?q?s.py:=20-=20OfficialKyosaiField=20-=20=E5=85=B1=E6=B8=88?= =?UTF-8?q?=E3=83=9E=E3=82=B9=E3=82=BF=20-=20OfficialChusankanField=20-=20?= =?UTF-8?q?=E4=B8=AD=E5=B1=B1=E9=96=93=E3=83=9E=E3=82=B9=E3=82=BF=20-=20Fi?= =?UTF-8?q?eld=20-=20=E5=9C=83=E5=A0=B4=EF=BC=88PointField=20=E3=81=A7?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E6=83=85=E5=A0=B1=E5=AF=BE=E5=BF=9C=EF=BC=89?= =?UTF-8?q?=20plans/models.py:=20-=20Crop=20-=20=E4=BD=9C=E7=89=A9?= =?UTF-8?q?=E3=83=9E=E3=82=B9=E3=82=BF=20-=20Variety=20-=20=E5=93=81?= =?UTF-8?q?=E7=A8=AE=E3=83=9E=E3=82=B9=E3=82=BF=EF=BC=88unique=5Ftogether?= =?UTF-8?q?=20=E5=88=B6=E7=B4=84=EF=BC=89=20-=20Plan=20-=20=E4=BD=9C?= =?UTF-8?q?=E4=BB=98=E3=81=91=E8=A8=88=E7=94=BB=EF=BC=88unique=5Ftogether?= =?UTF-8?q?=20=E5=88=B6=E7=B4=84=EF=BC=89=20admin.py=20-=20=E5=85=A8?= =?UTF-8?q?=E3=81=A6=E3=81=AE=E3=83=A2=E3=83=87=E3=83=AB=E3=82=92=20admin?= =?UTF-8?q?=20=E3=81=AB=E7=99=BB=E9=8C=B2=20-=20list=5Fdisplay,=20search?= =?UTF-8?q?=5Ffields,=20list=5Ffilter=20=E8=A8=AD=E5=AE=9A=20-=20FieldAdmi?= =?UTF-8?q?n=20=E3=81=AF=20GIS=20=E7=94=A8=20GISModelAdmin=20=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20=E3=83=9E=E3=82=A4=E3=82=B0=E3=83=AC?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=20=E2=9C=85=20makemigratio?= =?UTF-8?q?ns=20-=20=E6=88=90=E5=8A=9F=20=E2=9C=85=20migrate=20-=20?= =?UTF-8?q?=E6=88=90=E5=8A=9F=20=E7=AE=A1=E7=90=86=E7=94=BB=E9=9D=A2=20(ht?= =?UTF-8?q?tp://localhost:8000/admin)=20=E3=81=A7=E5=85=A8=E3=81=A6?= =?UTF-8?q?=E3=81=AE=E3=83=A2=E3=83=87=E3=83=AB=E3=81=8C=E7=A2=BA=E8=AA=8D?= =?UTF-8?q?=E3=83=BB=E7=B7=A8=E9=9B=86=E3=81=A7=E3=81=8D=E3=81=BE=E3=81=99?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/apps/fields/admin.py | 24 ++++++- .../apps/fields/migrations/0001_initial.py | 65 +++++++++++++++++++ backend/apps/fields/models.py | 64 +++++++++++++++++- backend/apps/plans/admin.py | 22 ++++++- backend/apps/plans/migrations/0001_initial.py | 57 ++++++++++++++++ backend/apps/plans/models.py | 42 +++++++++++- 6 files changed, 270 insertions(+), 4 deletions(-) create mode 100644 backend/apps/fields/migrations/0001_initial.py create mode 100644 backend/apps/plans/migrations/0001_initial.py diff --git a/backend/apps/fields/admin.py b/backend/apps/fields/admin.py index 8c38f3f..4f55bb1 100644 --- a/backend/apps/fields/admin.py +++ b/backend/apps/fields/admin.py @@ -1,3 +1,25 @@ from django.contrib import admin +from django.contrib.gis import admin as gis_admin +from .models import OfficialKyosaiField, OfficialChusankanField, Field -# Register your models here. + +@admin.register(OfficialKyosaiField) +class OfficialKyosaiFieldAdmin(admin.ModelAdmin): + list_display = ('k_num', 's_num', 'kanji_name', 'address', 'area') + search_fields = ('k_num', 'kanji_name', 'address') + list_filter = ('s_num',) + + +@admin.register(OfficialChusankanField) +class OfficialChusankanFieldAdmin(admin.ModelAdmin): + list_display = ('c_id', 'oaza', 'aza', 'chiban', 'area', 'payment_amount') + search_fields = ('c_id', 'oaza', 'aza') + list_filter = ('oaza',) + + +@admin.register(Field) +class FieldAdmin(gis_admin.GISModelAdmin): + list_display = ('name', 'address', 'area_tan', 'area_m2', 'owner_name', 'kyosai_field', 'chusankan_field') + search_fields = ('name', 'address', 'owner_name') + list_filter = ('kyosai_field', 'chusankan_field') + gis_widget_kwargs = {'attrs': {'static_map': False, 'longitude': 139.0, 'latitude': 36.0, 'zoom': 10}} diff --git a/backend/apps/fields/migrations/0001_initial.py b/backend/apps/fields/migrations/0001_initial.py new file mode 100644 index 0000000..428635b --- /dev/null +++ b/backend/apps/fields/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 5.0 on 2026-02-15 02:45 + +import django.contrib.gis.db.models.fields +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='OfficialChusankanField', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('c_id', models.CharField(max_length=20, unique=True, verbose_name='中山間ID')), + ('oaza', models.CharField(max_length=100, verbose_name='大字')), + ('aza', models.CharField(max_length=100, verbose_name='字')), + ('chiban', models.CharField(max_length=50, verbose_name='地番')), + ('area', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='面積(ha)')), + ('payment_amount', models.DecimalField(blank=True, decimal_places=0, max_digits=12, null=True, verbose_name='支払金額')), + ], + options={ + 'verbose_name': '中山間マスタ', + 'verbose_name_plural': '中山間マスタ', + }, + ), + migrations.CreateModel( + name='OfficialKyosaiField', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('k_num', models.CharField(max_length=20, unique=True, verbose_name='共済番号')), + ('s_num', models.CharField(blank=True, max_length=20, null=True, verbose_name='枝番')), + ('address', models.CharField(max_length=255, verbose_name='住所')), + ('kanji_name', models.CharField(max_length=100, verbose_name='漢字名')), + ('area', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='面積(ha)')), + ], + options={ + 'verbose_name': '共済マスタ', + 'verbose_name_plural': '共済マスタ', + }, + ), + migrations.CreateModel( + name='Field', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='圃場名')), + ('address', models.CharField(max_length=255, verbose_name='住所')), + ('area_tan', models.DecimalField(decimal_places=4, max_digits=6, verbose_name='面積(反)')), + ('area_m2', models.IntegerField(verbose_name='面積(m2)')), + ('owner_name', models.CharField(max_length=100, verbose_name='所有者名')), + ('location', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='位置情報')), + ('chusankan_field', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fields', to='fields.officialchusankanfield', verbose_name='関連中山間マスタ')), + ('kyosai_field', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fields', to='fields.officialkyosaifield', verbose_name='関連共済マスタ')), + ], + options={ + 'verbose_name': '圃場', + 'verbose_name_plural': '圃場', + }, + ), + ] diff --git a/backend/apps/fields/models.py b/backend/apps/fields/models.py index 71a8362..eb48eaf 100644 --- a/backend/apps/fields/models.py +++ b/backend/apps/fields/models.py @@ -1,3 +1,65 @@ +from django.contrib.gis.db import models as gis_models from django.db import models -# Create your models here. + +class OfficialKyosaiField(models.Model): + k_num = models.CharField(max_length=20, unique=True, verbose_name="共済番号") + s_num = models.CharField(max_length=20, blank=True, null=True, verbose_name="枝番") + address = models.CharField(max_length=255, verbose_name="住所") + kanji_name = models.CharField(max_length=100, verbose_name="漢字名") + area = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="面積(ha)") + + class Meta: + verbose_name = "共済マスタ" + verbose_name_plural = "共済マスタ" + + def __str__(self): + return f"{self.k_num} ({self.kanji_name})" + + +class OfficialChusankanField(models.Model): + c_id = models.CharField(max_length=20, unique=True, verbose_name="中山間ID") + oaza = models.CharField(max_length=100, verbose_name="大字") + aza = models.CharField(max_length=100, verbose_name="字") + chiban = models.CharField(max_length=50, verbose_name="地番") + area = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="面積(ha)") + payment_amount = models.DecimalField(max_digits=12, decimal_places=0, blank=True, null=True, verbose_name="支払金額") + + class Meta: + verbose_name = "中山間マスタ" + verbose_name_plural = "中山間マスタ" + + def __str__(self): + return f"{self.c_id} ({self.oaza}{self.aza})" + + +class Field(models.Model): + name = models.CharField(max_length=100, verbose_name="圃場名") + address = models.CharField(max_length=255, verbose_name="住所") + area_tan = models.DecimalField(max_digits=6, decimal_places=4, verbose_name="面積(反)") + area_m2 = models.IntegerField(verbose_name="面積(m2)") + owner_name = models.CharField(max_length=100, verbose_name="所有者名") + kyosai_field = models.ForeignKey( + OfficialKyosaiField, + on_delete=models.SET_NULL, + blank=True, + null=True, + related_name='fields', + verbose_name="関連共済マスタ" + ) + chusankan_field = models.ForeignKey( + OfficialChusankanField, + on_delete=models.SET_NULL, + blank=True, + null=True, + related_name='fields', + verbose_name="関連中山間マスタ" + ) + location = gis_models.PointField(blank=True, null=True, verbose_name="位置情報") + + class Meta: + verbose_name = "圃場" + verbose_name_plural = "圃場" + + def __str__(self): + return self.name diff --git a/backend/apps/plans/admin.py b/backend/apps/plans/admin.py index 8c38f3f..72c2ef6 100644 --- a/backend/apps/plans/admin.py +++ b/backend/apps/plans/admin.py @@ -1,3 +1,23 @@ from django.contrib import admin +from .models import Crop, Variety, Plan -# Register your models here. + +@admin.register(Crop) +class CropAdmin(admin.ModelAdmin): + list_display = ('name',) + search_fields = ('name',) + + +@admin.register(Variety) +class VarietyAdmin(admin.ModelAdmin): + list_display = ('crop', 'name') + search_fields = ('name', 'crop__name') + list_filter = ('crop',) + + +@admin.register(Plan) +class PlanAdmin(admin.ModelAdmin): + list_display = ('field', 'year', 'crop', 'variety', 'notes') + search_fields = ('field__name', 'crop__name', 'variety__name') + list_filter = ('year', 'crop') + ordering = ('-year', 'field') diff --git a/backend/apps/plans/migrations/0001_initial.py b/backend/apps/plans/migrations/0001_initial.py new file mode 100644 index 0000000..52cd30b --- /dev/null +++ b/backend/apps/plans/migrations/0001_initial.py @@ -0,0 +1,57 @@ +# Generated by Django 5.0 on 2026-02-15 02:45 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('fields', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Crop', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True, verbose_name='作物名')), + ], + options={ + 'verbose_name': '作物マスタ', + 'verbose_name_plural': '作物マスタ', + }, + ), + migrations.CreateModel( + name='Variety', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='品種名')), + ('crop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='varieties', to='plans.crop', verbose_name='作物')), + ], + options={ + 'verbose_name': '品種マスタ', + 'verbose_name_plural': '品種マスタ', + 'unique_together': {('crop', 'name')}, + }, + ), + migrations.CreateModel( + name='Plan', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('year', models.IntegerField(verbose_name='作付年度')), + ('notes', models.TextField(blank=True, null=True, verbose_name='備考')), + ('crop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plans', to='plans.crop', verbose_name='作物')), + ('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plans', to='fields.field', verbose_name='圃場')), + ('variety', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plans', to='plans.variety', verbose_name='品種')), + ], + options={ + 'verbose_name': '作付け計画', + 'verbose_name_plural': '作付け計画', + 'ordering': ['-year', 'field'], + 'unique_together': {('field', 'year')}, + }, + ), + ] diff --git a/backend/apps/plans/models.py b/backend/apps/plans/models.py index 71a8362..2521baf 100644 --- a/backend/apps/plans/models.py +++ b/backend/apps/plans/models.py @@ -1,3 +1,43 @@ from django.db import models +from apps.fields.models import Field -# Create your models here. + +class Crop(models.Model): + name = models.CharField(max_length=100, unique=True, verbose_name="作物名") + + class Meta: + verbose_name = "作物マスタ" + verbose_name_plural = "作物マスタ" + + def __str__(self): + return self.name + + +class Variety(models.Model): + crop = models.ForeignKey(Crop, on_delete=models.CASCADE, related_name='varieties', verbose_name="作物") + name = models.CharField(max_length=100, verbose_name="品種名") + + class Meta: + verbose_name = "品種マスタ" + verbose_name_plural = "品種マスタ" + unique_together = [['crop', 'name']] + + def __str__(self): + return f"{self.crop.name} - {self.name}" + + +class Plan(models.Model): + field = models.ForeignKey(Field, on_delete=models.CASCADE, related_name='plans', verbose_name="圃場") + year = models.IntegerField(verbose_name="作付年度") + crop = models.ForeignKey(Crop, on_delete=models.CASCADE, related_name='plans', verbose_name="作物") + variety = models.ForeignKey(Variety, on_delete=models.CASCADE, related_name='plans', verbose_name="品種") + notes = models.TextField(blank=True, null=True, verbose_name="備考") + + class Meta: + verbose_name = "作付け計画" + verbose_name_plural = "作付け計画" + unique_together = [['field', 'year']] + ordering = ['-year', 'field'] + + def __str__(self): + return f"{self.field.name} - {self.year} - {self.crop.name}"