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}"