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

如何获取各投票项的所有候选人及解决数据展示与投票权限问题?

Django投票系统问题排查与修复

问题描述

  • 需要获取每个投票项(Position)对应的所有候选人选项
  • 当前创建了两个投票项,前端仅展示第二个投票项的候选人数据,第一个仅能在终端打印,无法传递到前端
  • 需实现选民完成某一投票项后仍可参与其他投票项,已用Voter模型的ManyToManyField关联Position,但功能未实现

相关代码

Models代码

class Voter(models.Model):
    gender_type = (('male', 'Male'), ('female', 'Female'))
    admin = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
    gender = models.CharField(max_length=10, choices=gender_type)
    country_region = models.CharField(max_length=255, choices=Countries.choices, default=Countries.Pakistan)
    phone = PhoneNumberField(blank=True, unique=True)
    otp = models.CharField(max_length=10, null=True)
    verified = models.BooleanField(default=False)
    voted = models.BooleanField(default=False)
    otp_sent = models.IntegerField(default=0)  # Control how many OTPs are sent
    ip_address = models.GenericIPAddressField(unique=True, default='ABC')
    position = models.ManyToManyField('Position', blank=True)

    def __str__(self):
        return self.admin.first_name

    def get_voted_position(self):
        pass


class Position(models.Model):
    ACTIVE = 0
    INACTIVE = 1

    status_choice = ((ACTIVE, 'Active'), (INACTIVE, 'Inactive'))
    name = models.CharField(max_length=50, unique=True)
    max_vote = models.IntegerField()
    priority = models.IntegerField()
    description = models.TextField(null=True, blank=True)
    status = models.PositiveBigIntegerField(default=0, choices=status_choice)

    def __str__(self):
        return self.name

    def participants(self):
        participants = 0
        candidates = self.candidates.all()
        for candidate in candidates:
            participants += candidate.votes
        
        return participants


class Candidate(models.Model):
    fullname = models.CharField(max_length=50)
    photo = models.ImageField(upload_to="candidates")
    bio = models.TextField()
    position = models.ForeignKey(Position, on_delete=models.CASCADE, related_name='candidates')
    # votes = models.IntegerField(default=0)

    def __str__(self):
        return self.fullname

    def calculate_votes(self):
        votes = self.votes.all()
        total_votes = 0
        for vote in votes:
            total_votes += vote.counter
        
        return total_votes


class Votes(models.Model):
    voter = models.ForeignKey(Voter, on_delete=models.CASCADE)
    position = models.ForeignKey(Position, on_delete=models.CASCADE, related_name='positions')
    candidate = models.ForeignKey(Candidate, on_delete=models.CASCADE, related_name='votes')
    counter = models.IntegerField(default=0)


class BlockIP(models.Model):
    voter = models.ForeignKey(Voter, on_delete=models.CASCADE, related_name='ip')
    position = models.ForeignKey(Position, on_delete=models.CASCADE, related_name='positions_ip', null=True, blank=True)
    ip_address = models.GenericIPAddressField(default='abc')


@receiver(post_save, sender=Voter)
def default_to_non_active(sender, instance, created, **kwargs):
    if created:
        instance = BlockIP.objects.create(voter=instance, ip_address=instance.ip_address)
        instance.save()

视图函数代码

def index(request):
    context = {}
    instruction = ""
    candidate_data = []
    positions = Position.objects.order_by('priority').all()
   
    for position in positions:
        query = position.candidates.all()
        if position.max_vote > 1:
            instruction = "You may select up to " + str(position.max_vote) + " candidates"
        else:
            instruction = "Select a candidate"


    context = {
        'positions': positions,
        'query': query,
        'candidate_data': candidate_data,
        'instruction': instruction,
    }
    return render(request, 'poll/index.html', context)

前端模板代码

<div class="row">
  <div class="mt-5">
    {% for position in positions %}
    {{ position.name }} <br>
    {{ instruction }} <br>
    {% endfor %}
    {% for c in query %}
        {{ c.fullname }}
        {{ c.id }}
        <p>{{ c.bio }}</p>
        <img src="/media/{{ c.photo }}" alt="" width="200" height="200">
      {% endfor %}
       
  </div>
</div>

问题修复方案

1. 展示所有投票项的候选人数据

当前视图中query变量在循环中被反复覆盖,最终仅保留最后一个投票项的候选人数据。需重构视图,将每个投票项的候选人、指令打包后传递给前端。

修改视图函数:

def index(request):
    positions_with_data = []
    positions = Position.objects.order_by('priority').all()
   
    for position in positions:
        candidates = position.candidates.all()
        # 生成当前投票项的指令
        instruction = f"You may select up to {position.max_vote} candidates" if position.max_vote > 1 else "Select a candidate"
        # 打包投票项相关数据
        positions_with_data.append({
            'position': position,
            'candidates': candidates,
            'instruction': instruction
        })

    context = {'positions_with_data': positions_with_data}
    return render(request, 'poll/index.html', context)

修改前端模板,遍历每个投票项的完整数据:

<div class="row">
  <div class="mt-5">
    {% for item in positions_with_data %}
      <h3>{{ item.position.name }}</h3>
      <p>{{ item.instruction }}</p>
      {% for c in item.candidates %}
        <div class="candidate-item">
          {{ c.fullname }} (ID: {{ c.id }})
          <p>{{ c.bio }}</p>
          <img src="/media/{{ c.photo }}" alt="{{ c.fullname }}" width="200" height="200">
        </div>
      {% endfor %}
      <hr>
    {% endfor %}
  </div>
</div>

2. 实现多投票项的投票权限控制

完善Voter模型的方法,用于跟踪已投投票项;在投票逻辑中添加权限校验,并更新选民的已投记录。

更新Voter模型:

class Voter(models.Model):
    # ... 保留原有字段 ...

    def get_voted_positions(self):
        return self.position.all()

    def has_voted_for_position(self, position):
        return position in self.get_voted_positions()

投票处理视图示例:

def cast_vote(request, position_id, candidate_id):
    voter = request.user.voter  # 假设已通过登录关联Voter实例
    position = Position.objects.get(id=position_id)
    candidate = Candidate.objects.get(id=candidate_id)

    if voter.has_voted_for_position(position):
        return HttpResponse("您已完成该投票项的投票")
    
    # 创建投票记录
    Votes.objects.create(voter=voter, position=position, candidate=candidate, counter=1)
    # 更新已投投票项列表
    voter.position.add(position)
    voter.save()

    return redirect('index')

前端模板中添加投票状态判断:

{% for item in positions_with_data %}
  <h3>{{ item.position.name }}</h3>
  {% if request.user.voter.has_voted_for_position(item.position) %}
    <p>您已完成该投票项的投票</p>
  {% else %}
    <p>{{ item.instruction }}</p>
    {% for c in item.candidates %}
      <button onclick="castVote({{ item.position.id }}, {{ c.id }})">投票给{{ c.fullname }}</button>
    {% endfor %}
  {% endif %}
  <hr>
{% endfor %}

3. 修复Position模型的participants方法

原方法中直接累加candidate.votes(关联对象集合)会报错,需调用Candidate的calculate_votes方法:

class Position(models.Model):
    # ... 保留原有字段 ...

    def participants(self):
        total_votes = 0
        for candidate in self.candidates.all():
            total_votes += candidate.calculate_votes()
        return total_votes

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 10:40:18