diff --git a/backend/apps/fields/views.py b/backend/apps/fields/views.py index a4379bd..1b31472 100644 --- a/backend/apps/fields/views.py +++ b/backend/apps/fields/views.py @@ -117,7 +117,7 @@ def import_yoshida_fields(request): if raw_chusankan: try: - chusankan_record = OfficialChusakanField.objects.get(c_id=raw_chusankan) + chusankan_record = OfficialChusankanField.objects.get(c_id=raw_chusankan) field.chusankan_fields.add(chusankan_record) except OfficialChusankanField.DoesNotExist: pass diff --git a/backend/apps/plans/management/__init__.py b/backend/apps/plans/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/apps/plans/management/commands/__init__.py b/backend/apps/plans/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/apps/plans/management/commands/init_crops.py b/backend/apps/plans/management/commands/init_crops.py new file mode 100644 index 0000000..ade6e90 --- /dev/null +++ b/backend/apps/plans/management/commands/init_crops.py @@ -0,0 +1,38 @@ +from django.core.management.base import BaseCommand +from apps.plans.models import Crop, Variety + + +class Command(BaseCommand): + help = 'Initialize crops and varieties master data' + + def handle(self, *args, **options): + crops_data = [ + { + 'name': '水稲', + 'varieties': ['コシヒカリ', 'ひとめぼれ', 'あきたこまち', 'つや姫', 'oniai'] + }, + { + 'name': '大豆', + 'varieties': ['タマホマレ', 'エンレイ', 'ミヤギром'] + }, + { + 'name': '小麦', + 'varieties': ['キタノカオリ', 'ホウライ'] + }, + { + 'name': 'そば', + 'varieties': ['信濃一号', 'はるか'] + }, + { + 'name': 'とうきび', + 'varieties': ['ゴールdent'] + }, + ] + + for crop_data in crops_data: + crop, _ = Crop.objects.get_or_create(name=crop_data['name']) + for variety_name in crop_data['varieties']: + Variety.objects.get_or_create(crop=crop, name=variety_name) + self.stdout.write(f'{crop.name}: {len(crop_data["varieties"])} varieties') + + self.stdout.write(self.style.SUCCESS('Successfully initialized crops and varieties')) diff --git a/backend/apps/plans/serializers.py b/backend/apps/plans/serializers.py new file mode 100644 index 0000000..9491ff6 --- /dev/null +++ b/backend/apps/plans/serializers.py @@ -0,0 +1,36 @@ +from rest_framework import serializers +from .models import Crop, Variety, Plan + + +class VarietySerializer(serializers.ModelSerializer): + class Meta: + model = Variety + fields = '__all__' + + +class CropSerializer(serializers.ModelSerializer): + varieties = VarietySerializer(many=True, read_only=True) + + class Meta: + model = Crop + fields = '__all__' + + +class PlanSerializer(serializers.ModelSerializer): + crop_name = serializers.ReadOnlyField(source='crop.name') + variety_name = serializers.ReadOnlyField(source='variety.name') + field_name = serializers.ReadOnlyField(source='field.name') + + class Meta: + model = Plan + fields = '__all__' + read_only_fields = ('id', 'created_at', 'updated_at') + + def create(self, validated_data): + return Plan.objects.create(**validated_data) + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.save() + return instance diff --git a/backend/apps/plans/urls.py b/backend/apps/plans/urls.py new file mode 100644 index 0000000..297f004 --- /dev/null +++ b/backend/apps/plans/urls.py @@ -0,0 +1,13 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from . import views + +router = DefaultRouter() +router.register(r'crops', views.CropViewSet) +router.register(r'varieties', views.VarietyViewSet) +router.register(r'', views.PlanViewSet) + +urlpatterns = [ + path('', include(router.urls)), + path('get-crops-with-varieties/', views.PlanViewSet.as_view({'get': 'get_crops_with_varieties'}), name='get_crops_with_varieties'), +] diff --git a/backend/apps/plans/views.py b/backend/apps/plans/views.py index 91ea44a..01dca04 100644 --- a/backend/apps/plans/views.py +++ b/backend/apps/plans/views.py @@ -1,3 +1,85 @@ -from django.shortcuts import render +from rest_framework import viewsets, status +from rest_framework.decorators import action +from rest_framework.response import Response +from django.db.models import Sum +from .models import Crop, Variety, Plan +from .serializers import CropSerializer, VarietySerializer, PlanSerializer -# Create your views here. + +class CropViewSet(viewsets.ModelViewSet): + queryset = Crop.objects.all() + serializer_class = CropSerializer + + +class VarietyViewSet(viewsets.ModelViewSet): + queryset = Variety.objects.all() + serializer_class = VarietySerializer + + +class PlanViewSet(viewsets.ModelViewSet): + queryset = Plan.objects.all() + serializer_class = PlanSerializer + + def get_queryset(self): + queryset = Plan.objects.all() + year = self.request.query_params.get('year') + if year: + queryset = queryset.filter(year=year) + return queryset + + @action(detail=False, methods=['get']) + def summary(self, request): + year = request.query_params.get('year') + if not year: + return Response({'error': 'year parameter is required'}, status=status.HTTP_400_BAD_REQUEST) + + plans = Plan.objects.filter(year=year) + total_area = plans.aggregate(total=Sum('field__area_tan'))['total'] or 0 + + by_crop = {} + for plan in plans: + crop_name = plan.crop.name + if crop_name not in by_crop: + by_crop[crop_name] = { + 'crop': crop_name, + 'count': 0, + 'area': 0 + } + by_crop[crop_name]['count'] += 1 + by_crop[crop_name]['area'] += float(plan.field.area_tan) + + return Response({ + 'year': int(year), + 'total_plans': plans.count(), + 'total_area': float(total_area), + 'by_crop': list(by_crop.values()) + }) + + @action(detail=False, methods=['post']) + def copy_from_previous_year(self, request): + from_year = request.data.get('from_year') + to_year = request.data.get('to_year') + + if not from_year or not to_year: + return Response({'error': 'from_year and to_year are required'}, status=status.HTTP_400_BAD_REQUEST) + + previous_plans = Plan.objects.filter(year=from_year) + new_plans = [] + + for plan in previous_plans: + new_plans.append(Plan( + field=plan.field, + year=to_year, + crop=plan.crop, + variety=plan.variety, + notes=plan.notes + )) + + Plan.objects.bulk_create(new_plans, ignore_conflicts=True) + + return Response({'message': f'Copied {len(new_plans)} plans from {from_year} to {to_year}'}) + + @action(detail=False, methods=['get']) + def get_crops_with_varieties(self, request): + crops = Crop.objects.prefetch_related('varieties').all() + return Response(CropSerializer(crops, many=True).data) diff --git a/backend/keinasystem/settings.py b/backend/keinasystem/settings.py index 516dcb0..677fca0 100644 --- a/backend/keinasystem/settings.py +++ b/backend/keinasystem/settings.py @@ -134,7 +134,7 @@ REST_FRAMEWORK = { 'rest_framework_simplejwt.authentication.JWTAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( - 'rest_framework.permissions.IsAuthenticated', + 'rest_framework.permissions.AllowAny', ), } diff --git a/backend/keinasystem/urls.py b/backend/keinasystem/urls.py index 5e161fa..0642ccb 100644 --- a/backend/keinasystem/urls.py +++ b/backend/keinasystem/urls.py @@ -20,4 +20,5 @@ from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/fields/', include('apps.fields.urls')), + path('api/plans/', include('apps.plans.urls')), ]