DRF技术问询:提交JobPlanManager时如何创建/更新关联JobPlan
Alright, let's break down how to implement this logic in Django REST Framework step by step. I've dealt with similar overlapping period scenarios before, so here's a practical approach that covers your use case:
1. Model Setup
First, let's define the core models to map your relationships clearly:
from django.db import models from django.contrib.auth.models import User class Job(models.Model): name = models.CharField(max_length=255) # Add any other fields relevant to your job (description, status, etc.) class JobPlan(models.Model): # One-to-one with Job since each Job has exactly one plan job = models.OneToOneField(Job, on_delete=models.CASCADE, related_name="job_plan") start_date = models.DateField() end_date = models.DateField() class JobPlanManager(models.Model): # Many managers can be linked to a single JobPlan job_plan = models.ForeignKey(JobPlan, on_delete=models.CASCADE, related_name="managers") user = models.ForeignKey(User, on_delete=models.CASCADE) # Individual manager's responsibility period manager_start_date = models.DateField() manager_end_date = models.DateField()
2. Serializer Implementation
We'll create a serializer that accepts the job ID, user ID, and manager's period, while handling validation for valid date ranges:
from rest_framework import serializers from .models import Job, JobPlan, JobPlanManager class JobPlanManagerSerializer(serializers.ModelSerializer): # Expose job and user IDs for easy input job_id = serializers.PrimaryKeyRelatedField( queryset=Job.objects.all(), source="job_plan.job" ) user_id = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), source="user" ) class Meta: model = JobPlanManager fields = ["id", "job_id", "user_id", "manager_start_date", "manager_end_date"] def validate(self, data): # Ensure manager's start date doesn't come after end date if data["manager_start_date"] > data["manager_end_date"]: raise serializers.ValidationError( "Manager start date cannot be later than end date." ) return data
3. View Logic (Core Business Logic)
We'll use a CreateAPIView (and extend to UpdateAPIView if needed) to handle the creation of JobPlanManager instances, with logic to create/update the associated JobPlan:
from rest_framework import generics from django.db import transaction from .models import JobPlan, JobPlanManager from .serializers import JobPlanManagerSerializer class JobPlanManagerCreateView(generics.CreateAPIView): queryset = JobPlanManager.objects.all() serializer_class = JobPlanManagerSerializer @transaction.atomic def perform_create(self, serializer): # Extract validated data job = serializer.validated_data["job_plan"]["job"] manager_start = serializer.validated_data["manager_start_date"] manager_end = serializer.validated_data["manager_end_date"] # Get or create the JobPlan for the Job job_plan, is_new = JobPlan.objects.get_or_create(job=job) # Update JobPlan dates to cover the new manager's period if is_new: # If it's a new plan, use the manager's dates directly job_plan.start_date = manager_start job_plan.end_date = manager_end else: # For existing plans, extend to the earliest start and latest end job_plan.start_date = min(job_plan.start_date, manager_start) job_plan.end_date = max(job_plan.end_date, manager_end) job_plan.save() # Link the new JobPlanManager to the updated JobPlan serializer.save(job_plan=job_plan)
Handling Updates to Existing JobPlanManagers
If you need to support updating a manager's period and automatically adjusting the JobPlan, extend the view to include UpdateAPIView and reuse the date logic:
class JobPlanManagerCreateUpdateView(generics.CreateAPIView, generics.UpdateAPIView): queryset = JobPlanManager.objects.all() serializer_class = JobPlanManagerSerializer @transaction.atomic def perform_create(self, serializer): self._sync_job_plan_dates(serializer) serializer.save(job_plan=self.job_plan) @transaction.atomic def perform_update(self, serializer): self._sync_job_plan_dates(serializer) serializer.save(job_plan=self.job_plan) def _sync_job_plan_dates(self, serializer): job = serializer.validated_data["job_plan"]["job"] manager_start = serializer.validated_data["manager_start_date"] manager_end = serializer.validated_data["manager_end_date"] job_plan, is_new = JobPlan.objects.get_or_create(job=job) # Calculate new date range new_start = manager_start if is_new else min(job_plan.start_date, manager_start) new_end = manager_end if is_new else max(job_plan.end_date, manager_end) job_plan.start_date = new_start job_plan.end_date = new_end job_plan.save() self.job_plan = job_plan
4. Extra: Adjust JobPlan When Managers Are Deleted
To keep the JobPlan dates accurate if a manager is removed, add a post-delete signal to recalculate the date range:
from django.db.models.signals import post_delete from django.dispatch import receiver @receiver(post_delete, sender=JobPlanManager) def update_job_plan_after_manager_removal(sender, instance, **kwargs): job_plan = instance.job_plan remaining_managers = job_plan.managers.all() if remaining_managers.exists(): # Recalculate to the earliest start and latest end of remaining managers new_start = min(m.manager_start_date for m in remaining_managers) new_end = max(m.manager_end_date for m in remaining_managers) job_plan.start_date = new_start job_plan.end_date = new_end job_plan.save() else: # Optional: Delete the JobPlan if no managers are left (adjust based on your needs) job_plan.delete()
Key Notes
- The
@transaction.atomicdecorator ensures that theJobPlanupdate andJobPlanManagercreation/update happen atomically, preventing partial data saves if something fails. - The logic automatically extends the
JobPlanto cover all overlapping or extended manager periods, which matches your example where User 2 extends the job's timeline.
内容的提问来源于stack exchange,提问作者MarkD

