在 Django 中编写函数的 pythonic 和 MVC 友好方法是什么?

What are the pythonic & MVC friendly ways to write functions in Django?

我正在使用 Django 1.8.4 创建一个简单的应用程序,它允许用户共享来自其他网站的链接以及投票赞成和反对的功能。有一些可重用的应用程序可以处理这个问题,但我更喜欢将自己的代码编写到 "learn" Django。但是经过几天的尝试,我认为我的代码很糟糕。它几乎可以正常工作,但对我来说似乎不是真正的 pythonic,而且我认为这不是在 Django 中做事的正确方法。

这是处理选票的代码部分:

models.py

class Vote(models.Model):
    UP, DOWN = range(2)
    TYPE_CHOICES = [(UP, "Upvote"), (DOWN, "DownVote")]

    voter = models.ForeignKey(User)
    link = models.ForeignKey(Link, related_name='votes')
    vote_type = models.IntegerField(choices=TYPE_CHOICES, db_index=True)
    vote_date = models.DateTimeField(db_index=True, auto_now=True)

然后我将这个模型传递给一个简单的表单,这是模板代码: link_detail.html

<form method="POST" action="{% url 'vote' %}" class="vote_form">
            {% csrf_token %}
            <input type="hidden" id="id_link" name="link" class="hidden_id" value="{{ link.pk }}"/>
            <input type="hidden" id="id_link" name="voter" class="hidden_id" value="{{ user.pk }}"/>
            <input type="hidden" id="id_link" name="vote_type" class="hidden_id" value="0"/>        
            <button> + </button>
            [up votes: {{ link.up_votes}}]
        </form>

        <form method="POST" action="{% url 'vote' %}" class="vote_form">
            {% csrf_token %}
            <input type="hidden" id="id_link" name="link" class="hidden_id" value="{{ link.pk }}"/>
            <input type="hidden" id="id_link" name="voter" class="hidden_id" value="{{ user.pk }}"/>
            <input type="hidden" id="id_link" name="vote_type" class="hidden_id" value="1"/>        
            <button> - </button>
            [down votes: {{ link.down_votes }}]
        </form>

对我来说最丑陋的部分是 views.py。我想我最好用更简单的函数改变许多重复的 if 表达式:

class VoteFormView(FormView):
    form_class = VoteForm

    def form_valid(self, form):
        v_user = self.request.user
        v_link = get_object_or_404(Link, pk=form.data["link"])
        v_type = form.data['vote_type']

        up_votes = Vote.objects.filter(voter=v_user, link=v_link, vote_type=0)
        down_votes = Vote.objects.filter(voter=v_user, link=v_link, vote_type=1)

        up_voted = (up_votes.count() > 0)
        down_voted = (down_votes.count() > 0)

        if (v_type == "1") and not down_voted:
            print ("%s has not voted down_voted %s") % (v_user, v_link)
            Vote.objects.create(voter=v_user, link=v_link, vote_type=v_type)
            with transaction.atomic():
                Link.objects.filter(pk=form.data["link"]).update(down_votes=F('down_votes')+1)
            print("down voted")
        elif (v_type == "1") and down_voted:
            down_votes[0].delete()
            Link.objects.filter(pk=form.data["link"]).update(down_votes=F('down_votes')-1)
            print("unvoted")
        elif(v_type == "0") and not up_voted:
            print ("%s has not voted up_voted %s") % (v_user, v_link)
            Vote.objects.create(voter=v_user, link=v_link, vote_type=v_type)
            Link.objects.filter(pk=form.data["link"]).update(up_votes=F('up_votes')+1)
            print("voted")
        elif (v_type == "0") and up_voted:
            up_votes[0].delete()
            Link.objects.filter(pk=form.data["link"]).update(up_votes=F('up_votes')-1)
            print("unvoted")
        return redirect("home")

我刚刚看了一些其他应用程序,例如 django-vote,我觉得它们还不错。但这不是我想要的,实际上我完全不明白它是如何工作的。

综上所述,这是我的问题:我想写一些单独的函数,检查用户之前是否投票过,检查投票的类型,最后使用 F() 函数将投票应用于模型并提高用户的知名度。但是我不知道把这些代码放在哪里?例如在 models.py 或写新经理?

只是一些快速观察:

在我看来,您似乎正在放置使用视图中表单中的值的代码。那么为什么不将该逻辑放在表单本身中,可能放在 save() 方法中,因为 VoteForm 应该用于 saving/creating/updating 投票。

def save(self, *args, **kwargs):
    kwargs['commit'] = False
    vote = super(VoteForm, self).save(*args,**kwargs)

您不需要测试 count() > 0,因为空 QS 将 return 为假。我将逻辑分成两个嵌套的 if 语句,就好像 vtype 不是 0,而是 1.

if v_type == '0':
    if up_voted:
        <do stuff>
    else:
        <do downvoted stuff>
else:
    if up_voted:
        <do stuff>
    else:
        <do downvoted stuff>

请注意,您有一个未保存的 Vote returned 来自 super 的实例,因此您可以使用它来代替上面的创建语句。我还会制作一个可以 upvote/downvote 的 LinkManager class。您的 F 语句使用大概是为了提高速度,因为它不会将 Link 拉入内存。也不需要使用过滤器,因为 pk 是唯一的。

LinkManager(models.Manager):
    def update_up_votes(self, pk, increment=True):
        self.get(pk=pk).update(up_votes=F('up_votes')+1) if increment else self.get(pk=pk).update(up_votes=F('up_votes')-1)

希望其中的一些内容可以帮助您进行一些重构。

编辑:

您可以在代码中使用 LinkManager 方法代替 Link.objects.filter().update()。它将它重构给经理。所以为了投票

Link.objects.update_up_votes(pk)

或减少赞成票

Link.objects.update_up_votes(pk, increment=False)

请记住,您必须通过对象将自定义管理器添加到 Link class。

对于您的模板 - 我并没有真正查看它,但只需在 VoteForm 上执行 {{form.as_p}} 即可为您提供所有已格式化的字段。您不需要两种形式,因为它们 post 用于相同的视图。只需在视图中执行您的逻辑即可决定。您也不需要在那里访问用户,因为您的投票是通过 vote.voter.