Django多模型增改实现咨询:save()调用位置及优化方案
Hey there! Let's tackle this save() confusion you're having with your Django models. The core problem here is that you're manually handling too much of the model synchronization and save logic across multiple methods, which leads to those frustrating "multiple save clicks" scenarios where changes don't propagate properly. Let's refactor this to follow Django best practices and make your code more robust.
First, let's break down what's causing the issues in your current code:
- You're calling
save()multiple times in redundant places (e.g., inCircuit.create()and then again in your view'sform_valid()). - The
set_circuit()andset_medium()methods are unnecessarily passing the instance around—since they're instance methods, you can just useselfdirectly. - Updates to
Circuitaren't reliably triggering saves on the relatedMediummodel, leading to stale data.
Here's a cleaned-up, robust implementation that fixes these problems:
Step 1: Refactor the Models
We'll move all synchronization and calculation logic into the model's save() method (or use signals for a more decoupled approach) so that every time a model instance is saved, all related changes are handled automatically.
from django.db import models from django.db.models.signals import pre_save from django.dispatch import receiver import choices import services class Medium(models.Model): glycol_percentage = models.FloatField(default=0, choices=choices.GLYCOL_PERCENTAGE) temperature = models.FloatField(default=60, help_text='[degC]') rho = models.FloatField(default=1000, help_text='[kg/m3]') def save(self, *args, **kwargs): # Auto-calculate rho every time Medium is saved self.rho = services.rho(temperature=self.temperature, glycol_percentage=self.glycol_percentage) super().save(*args, **kwargs) class Circuit(models.Model): medium = models.ForeignKey('medium.Medium', related_name='medium_related', on_delete=models.CASCADE) temperature = models.FloatField(default=60, help_text='[degC]') glycol_percentage = models.FloatField(default=0, choices=choices.GLYCOL_PERCENTAGE) def save(self, *args, **kwargs): # Handle new Circuit instances: create a linked Medium automatically if not self.pk: self.medium = Medium.objects.create( temperature=self.temperature, glycol_percentage=self.glycol_percentage ) else: # Sync Circuit changes to the linked Medium and save it if (self.medium.temperature != self.temperature or self.medium.glycol_percentage != self.glycol_percentage): self.medium.temperature = self.temperature self.medium.glycol_percentage = self.glycol_percentage self.medium.save() # Save the Circuit itself super().save(*args, **kwargs)
Alternative: Use Signals for Decoupling
If you prefer to keep your model's save() method clean, you can use Django's pre_save signal to handle the Circuit-Medium synchronization:
# Add this to your models.py @receiver(pre_save, sender=Circuit) def sync_circuit_to_medium(sender, instance, **kwargs): if not instance.pk: # Create Medium for new Circuit instance.medium = Medium.objects.create( temperature=instance.temperature, glycol_percentage=instance.glycol_percentage ) else: # Update Medium if Circuit fields changed medium = instance.medium if medium.temperature != instance.temperature or medium.glycol_percentage != instance.glycol_percentage: medium.temperature = instance.temperature medium.glycol_percentage = instance.glycol_percentage medium.save() # Now your Circuit model's save() can be simplified: class Circuit(models.Model): # ... fields remain the same ... def save(self, *args, **kwargs): super().save(*args, **kwargs)
Step 2: Simplify Your Views
With the model logic handled properly, your views become much cleaner—we can rely on Django's built-in CreateView and UpdateView behavior instead of manually creating/saving instances:
from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic.edit import CreateView, UpdateView from django.urls import reverse_lazy from .models import Circuit from .forms import CircuitForm class CircuitCreateView(LoginRequiredMixin, CreateView): model = Circuit template_name = 'circuit/create.html' form_class = CircuitForm def get_success_url(self): # Redirect to the detail page of the newly created Circuit return reverse_lazy('circuit:detail', args=[self.object.pk]) class CircuitUpdateView(LoginRequiredMixin, UpdateView): model = Circuit template_name = 'circuit/detail-update.html' form_class = CircuitForm pk_url_kwarg = 'pk_circuit' # Match your URL pattern's parameter name def get_success_url(self): return reverse_lazy('circuit:detail', args=[self.object.pk])
Why This Works
- Single Source of Truth: All model-related logic lives in the model layer, so changes made anywhere (views, admin shell, etc.) will trigger the same synchronization.
- Automatic Propagation: When you save a
Circuit(either new or updated), the linkedMediumis automatically created or updated—no more clicking multiple save buttons. - Reduced Redundancy: We've removed the unnecessary
create()andset_*()methods, using Django's native ORM methods instead (likeobjects.create()andsave()). - Cleaner Views: Views focus on handling HTTP logic, not model synchronization, which makes them easier to maintain.
Key Takeaways
- Always keep model logic in the model layer (or signals) instead of views—this follows Django's DRY principle and ensures consistency.
- Avoid manual, repeated
save()calls; let Django's ORM handle the heavy lifting when possible. - For related models, use
save()overrides or signals to ensure changes propagate automatically.
内容的提问来源于stack exchange,提问作者arne

