Django多模型CreateView与ModelForm实现用户创建投票功能求助
Hey there! Let's break down your problems step by step—great job taking the official polls tutorial further by adding user-submitted polls, that's a perfect way to learn Django deeper.
1. Fixing Dynamic Choice Forms (No Fixed 3 Options)
Your current code hardcodes 3 choice fields, but you want users to add as many options as they need. Here's how to handle dynamic form fields and submit them all with one button:
Update views.py
We'll dynamically generate choice forms based on the number of fields submitted, and let users start with a default number of choices:
from django.utils import timezone def addpoll(request): if request.method == "POST": pform = PollForm(request.POST) # Get the number of choice fields submitted (from a hidden input) choice_count = int(request.POST.get('choice_count', 0)) cforms = [ChoiceForm(request.POST, prefix=f'choice-{x}') for x in range(choice_count)] ev_form = EligibleVotersForm(request.POST) # Validate all forms if pform.is_valid() and all(cf.is_valid() for cf in cforms) and ev_form.is_valid(): # Save the poll (associate with the logged-in user first!) new_poll = pform.save(commit=False) new_poll.created_by = request.user # You'll need to add a `created_by` ForeignKey to Poll (see tip below) new_poll.save() # Save all valid choices for cf in cforms: choice_text = cf.cleaned_data.get('choice_text') if choice_text: # Skip empty choice fields new_choice = cf.save(commit=False) new_choice.poll = new_poll new_choice.save() # Save eligible voters eligible_users = ev_form.cleaned_data['eligible_users'] for user in eligible_users: EligibleVoters.objects.get_or_create(user=user, poll=new_poll) return HttpResponseRedirect('/polls/') # Redirect to polls index after success # If invalid, keep track of how many choices were submitted else: choice_count = len([k for k in request.POST.keys() if k.startswith('choice-')]) else: # Initialize empty forms for GET request pform = PollForm() choice_count = 2 # Start with 2 choices by default cforms = [ChoiceForm(prefix=f'choice-{x}') for x in range(choice_count)] ev_form = EligibleVotersForm() return render(request, 'voting/newpoll.html', { 'poll_form': pform, 'choice_forms': cforms, 'ev_form': ev_form, 'choice_count': choice_count })
Update the Template (newpoll.html)
Add a hidden input to track the number of choices, plus JavaScript to let users add more options dynamically:
<form method="POST"> {% csrf_token %} <h2>Create a New Poll</h2> {{ poll_form.as_p }} <h3>Poll Options</h3> {% for form in choice_forms %} <div class="choice-group"> {{ form.as_p }} </div> {% endfor %} <input type="hidden" name="choice_count" id="choice-count" value="{{ choice_count }}"> <button type="button" id="add-choice-btn">Add Another Option</button> <h3>Eligible Voters</h3> {{ ev_form.as_p }} <button type="submit">Create Poll</button> </form> <script> document.getElementById('add-choice-btn').addEventListener('click', () => { const count = parseInt(document.getElementById('choice-count').value); const newCount = count + 1; document.getElementById('choice-count').value = newCount; // Create new choice field with the correct prefix const newChoiceDiv = document.createElement('div'); newChoiceDiv.className = 'choice-group'; newChoiceDiv.innerHTML = ` <p> <label for="id_choice-${newCount}-choice_text">Option ${newCount}:</label> <input type="text" name="choice-${newCount}-choice_text" id="id_choice-${newCount}-choice_text" maxlength="200" required> </p> `; document.querySelector('.choice-group:last-of-type').after(newChoiceDiv); }); </script>
2. Improving the EligibleVoters Model & Form
Your current model works, but we can fix a few edge cases and make the form more user-friendly:
Update the EligibleVoters Model
Add a uniqueness constraint to prevent duplicate user-poll entries, and clean up duplicate models (you have two Voter and PollVotes definitions—delete the duplicates!):
class EligibleVoters(models.Model): user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) poll = models.ForeignKey(Poll, on_delete=models.CASCADE) class Meta: unique_together = ('user', 'poll') # Stop users from being added multiple times to the same poll def __str__(self): return f"{self.user.username} - {self.poll.question_text}"
Update forms.py for Eligible Voters
Instead of a single-choice ModelForm, use a multiple-choice field so users can select multiple eligible voters at once:
from django.contrib.auth.models import User class PollForm(ModelForm): class Meta: model = Poll fields = ['question_text', 'is_active', 'activation_date', 'expiration_date'] # Optional: Add widgets for date fields to make them easier to use widgets = { 'activation_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}), 'expiration_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}), } class ChoiceForm(ModelForm): class Meta: model = Choice fields = ['choice_text'] class EligibleVotersForm(forms.Form): eligible_users = forms.ModelMultipleChoiceField( queryset=User.objects.filter(is_active=True), # Only show active users widget=forms.CheckboxSelectMultiple, # Checkboxes are easier for multiple selections label='Select Eligible Voters' )
3. Quick Recommendations
- Add a Creator Field to Poll: Add a
created_byForeignKey to your Poll model to track who made each poll:class Poll(models.Model): # ... your existing fields ... created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='created_polls') - Validate Poll Dates: Add custom validation to
PollFormto ensure activation/expiration dates make sense (e.g., active polls can't have a future activation date):class PollForm(ModelForm): # ... Meta class ... def clean(self): cleaned_data = super().clean() is_active = cleaned_data.get('is_active') activation_date = cleaned_data.get('activation_date') expiration_date = cleaned_data.get('expiration_date') if is_active: if not activation_date or activation_date > timezone.now(): self.add_error('activation_date', 'Active polls need an activation date in the past.') if expiration_date and expiration_date <= timezone.now(): self.add_error('expiration_date', 'Expiration date must be in the future.') return cleaned_data - Secure Key Fields: For your public/private key fields, use
TextFieldinstead ofCharFieldif you expect long values, and avoid storing unencrypted sensitive data—use Django'ssigningmodule or a library likecryptographyfor secure handling.
内容的提问来源于stack exchange,提问作者kstr

