基于外键筛选批量编辑Django现有Choice模型条目求助
Hey there! Let's get this sorted out for your Django 2.0.4 poll app. You're on the right track with modelformset—probably just missing a few key bits like filtering the formset to the specific Question and handling the foreign key association when saving. Here's a step-by-step working solution:
1. Confirm Your Model Setup (align with official tutorial)
First, make sure your polls/models.py matches the base structure (adjust if you've modified it):
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)
2. Create Choice Form & FormSet
Create a polls/forms.py file (if it doesn't exist) to define your form and formset:
from django import forms from django.forms import modelformset_factory from .models import Choice # Simple form for Choice - we only expose the editable field class ChoiceForm(forms.ModelForm): class Meta: model = Choice fields = ['choice_text'] # Generate a formset with 2 extra empty fields for new options ChoiceFormSet = modelformset_factory( Choice, form=ChoiceForm, extra=2, can_delete=False # Remove this line if you want to let users delete options )
3. Build the Login-Protected View
Add this view to polls/views.py to handle editing choices for a specific Question:
from django.shortcuts import get_object_or_404, render, redirect from django.contrib.auth.decorators import login_required from .models import Question from .forms import ChoiceFormSet @login_required def edit_choices(request, pk): # Fetch the target Question instance question = get_object_or_404(Question, pk=pk) if request.method == 'POST': # Initialize formset with POST data, filtered to this Question's choices formset = ChoiceFormSet( request.POST, queryset=Choice.objects.filter(question=question) ) if formset.is_valid(): # Save each form, ensuring the foreign key links to the current Question for form in formset: if form.cleaned_data: # Skip empty extra forms the user didn't fill choice = form.save(commit=False) choice.question = question choice.save() # Redirect back to the edit page (or to your question detail page) return redirect('polls:edit_choices', pk=pk) else: # Load existing choices for the Question on GET request formset = ChoiceFormSet(queryset=Choice.objects.filter(question=question)) return render(request, 'polls/edit_choices.html', { 'question': question, 'formset': formset })
4. Update Your URL Configuration
Add this pattern to polls/urls.py:
from django.urls import path from . import views app_name = 'polls' urlpatterns = [ # ... your existing URLs ... path('question/<int:pk>/edit-choices/', views.edit_choices, name='edit_choices'), ]
5. Create the Edit Template
Make polls/templates/polls/edit_choices.html to render the formset:
<!DOCTYPE html> <html> <head> <title>Edit Choices: {{ question.question_text }}</title> </head> <body> <h1>Edit Choices for "{{ question.question_text }}"</h1> <form method="post"> {% csrf_token %} {{ formset.management_form }} <!-- DO NOT OMIT THIS - critical for formset functionality --> {% for form in formset %} <div style="margin: 10px 0;"> {{ form.id }} <!-- Hidden field to track existing forms --> <label>Option {{ forloop.counter }}:</label> {{ form.choice_text }} {% if form.errors %} <span style="color: red; margin-left: 10px;">{{ form.choice_text.errors }}</span> {% endif %} </div> {% endfor %} <button type="submit">Save Changes</button> </form> <p style="margin-top: 20px;"><a href="{% url 'polls:detail' question.id %}">Back to Question</a></p> </body> </html>
Key Tips to Avoid Pitfalls:
- Never skip
formset.management_form: This hidden form contains metadata Django needs to process the formset correctly (like how many forms exist). Omitting it will throw errors. - Filter the queryset: By limiting the formset to the current Question's choices, you ensure you're only editing options tied to that specific question.
- Skip empty extra forms: The
if form.cleaned_datacheck prevents saving blank options if the user doesn't fill out the extra fields. - Login protection: The
@login_requireddecorator enforces your requirement that only logged-in users can access this page.
Test it out by navigating to /polls/question/<your-question-pk>/edit-choices/ after logging in—you'll see existing choices plus 2 empty fields to add new options. All edits and new choices will be saved to the correct Question.
内容的提问来源于stack exchange,提问作者johntkrpt

