Django中如何在ModelForm展示并保存关联模型的指定字段
Hey there, let's tackle this problem step by step based on what you've built so far! First, let's recap your existing work, then dive into the solution for displaying the MyProfile fields and saving the association correctly.
1. Custom User Model (MyProfile)
class MyProfile(AbstractUser): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) full_name = models.CharField(max_length=255) id_number = models.CharField(max_length=14) def __str__(self): return self.full_name def save(self, *args, **kwargs): self.full_name = '{0} {1}'.format(self.first_name, self.last_name) super().save(*args, **kwargs)
2. Associated Model (MyModel)
class MyModel(models.Model): member = models.OneToOneField(MyProfile, on_delete=models.CASCADE) description = models.CharField(max_length=255) def __str__(self): return self.member
3. Form Class (MyModelForm)
class MyModelForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) class Meta: model = MyModel fields = ('description') full_name = forms.CharField(required=False, label="", widget=forms.TextInput(attrs={'class': "form-control"})) id_number = forms.CharField(required=False, label="", widget=forms.TextInput(attrs={'class': "form-control"})) description = forms.CharField(required=False, label="", widget=forms.TextInput(attrs={'class': "form-control"}))
4. View Function (applicationdata)
def applicationdata(request): if request.method == 'POST': form = MyModelForm(request.POST or None) if form.is_valid(): form.save() return HttpResponseRedirect('/index/') else: print(form.errors) else: form = MyModelForm() context = { 'form': form, } return render(request, 'index.html', context)
How do I display MyProfile's full_name and id_number fields in MyModelForm instead of the default member field? And how do I save these values to the member association via the applicationdata view?
Update note: I've refined the AbstractUser model (added first_name/last_name), the project uses allauth, and I've updated the form's __init__ method.
Let's split this into two core tasks: showing the profile fields in your form, and linking them to the member relationship when saving.
1. Update MyModelForm to Display & Validate Profile Fields
We'll remove the auto-generated member field, make full_name/id_number required (to find the matching user), add validation to ensure the user exists, and override the save method to link the profile.
class MyModelForm(forms.ModelForm): # Mark these as required since we need them to locate the MyProfile full_name = forms.CharField(label="Full Name", widget=forms.TextInput(attrs={'class': "form-control"})) id_number = forms.CharField(label="ID Number", widget=forms.TextInput(attrs={'class': "form-control"})) description = forms.CharField(label="Description", widget=forms.TextInput(attrs={'class': "form-control"})) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Remove the default 'member' field so it doesn't show up in the form if 'member' in self.fields: del self.fields['member'] class Meta: model = MyModel # Exclude 'member' because we're handling it manually exclude = ('member',) # Custom validation to check if the provided full_name + id_number matches an existing user def clean(self): cleaned_data = super().clean() full_name = cleaned_data.get('full_name') id_number = cleaned_data.get('id_number') if full_name and id_number: try: # Find the matching MyProfile instance profile = MyProfile.objects.get(full_name=full_name, id_number=id_number) # Store the profile for use in the save method cleaned_data['profile'] = profile except MyProfile.DoesNotExist: raise forms.ValidationError("No user found with this full name and ID number.") return cleaned_data # Override save to assign the found profile to the member field def save(self, commit=True): instance = super().save(commit=False) # Link the validated profile to the MyModel instance instance.member = self.cleaned_data['profile'] if commit: instance.save() return instance
Key Changes:
- Removed
required=Falsefromfull_name/id_number(we need these to find the user) - Added a
cleanmethod to validate that the input matches an existingMyProfile - Overrode
saveto manually set thememberfield using the validated profile - Removed the default
memberfield from the form entirely
2. Tweak the applicationdata View
Your view is almost there—we'll make a small adjustment for cleaner redirect handling and ensure form errors are passed to the template:
from django.shortcuts import render, redirect from .forms import MyModelForm def applicationdata(request): if request.method == 'POST': form = MyModelForm(request.POST) if form.is_valid(): form.save() # Use redirect() instead of HttpResponseRedirect for cleaner code (ensure 'index' is a named URL) return redirect('index') # Invalid form? Errors will automatically be available in the template else: form = MyModelForm() return render(request, 'index.html', {'form': form})
Template Tips (index.html):
Make sure you display form errors and all fields properly:
<form method="POST"> {% csrf_token %} {{ form.as_p }} <!-- Show non-field errors (like the "no user found" message) --> {% if form.non_field_errors %} <div class="alert alert-danger"> {{ form.non_field_errors }} </div> {% endif %} <button type="submit" class="btn btn-primary">Save</button> </form>
3. Allauth-Specific Enhancement (Optional)
Since you're using allauth, you can auto-populate the full_name/id_number fields for logged-in users to avoid duplicate input. Update the form's __init__ method:
def __init__(self, *args, **kwargs): # Pull the logged-in user from kwargs user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if 'member' in self.fields: del self.fields['member'] # Auto-fill fields if user is authenticated if user and user.is_authenticated: self.fields['full_name'].initial = user.full_name self.fields['id_number'].initial = user.id_number # Make fields read-only if you don't want users to edit them self.fields['full_name'].widget.attrs['readonly'] = True self.fields['id_number'].widget.attrs['readonly'] = True
Then update the view to pass the user to the form:
def applicationdata(request): if request.method == 'POST': form = MyModelForm(request.POST, user=request.user) # ... rest of POST logic else: form = MyModelForm(user=request.user) # ... rest of the view
内容的提问来源于stack exchange,提问作者pije76

