You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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_by ForeignKey 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 PollForm to 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 TextField instead of CharField if you expect long values, and avoid storing unencrypted sensitive data—use Django's signing module or a library like cryptography for secure handling.

内容的提问来源于stack exchange,提问作者kstr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:28:27