首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Django对银行业务的看法

Django对银行业务的看法
EN

Code Review用户
提问于 2016-01-13 14:30:29
回答 2查看 1.5K关注 0票数 4

我如何能够编写/Djangoize/干燥我的Django代码?

views.py

代码语言:javascript
复制
class AccountDetail(DetailView):
    model = BankAccount
    template_name = 'bank/account_detail.html'

    def get(self, *args, **kwargs):
        self.object = self.get_object()

        if self.object.is_owner(self.request.user.citizen):
            return render(self.request, self.template_name, self.get_context_data())
        raise Http404


class SendTransfer(SingleObjectMixin, FormView):
    model = BankAccount
    form_class = SendTransferForm
    template_name = 'bank/send_transfer.html'

    def dispatch(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(SendTransfer, self).dispatch(request, *args, **kwargs)

    def get_object(self, queryset=None):
        obj = super(SendTransfer, self).get_object(queryset)

        if not obj.is_owner(self.request.user.citizen):
            raise Http404

        return obj

    def form_valid(self, form):
        data = form.cleaned_data
        MoneyTransfer.objects.create(sender=self.object,
                                     receiver=data['receiver'],  # ModelChoiceField in the form
                                     total=data['total'],  # FloatField in the form, etc.
                                     when=timezone.localtime(timezone.now()),
                                     comment=data['comment'])
        return redirect('AccountDetail', self.object.pk)

    def form_invalid(self, form):
        return render(self.request, self.template_name, self.get_context_data())

    def get_form_kwargs(self):
        kwargs = super(SendTransfer, self).get_form_kwargs()
        kwargs['sender'] = BankAccount.objects.get(id=self.kwargs['pk'])
        kwargs['user'] = self.request.user
        return kwargs


class OutcomeTransfers(DetailView):
    model = BankAccount
    template_name = 'bank/report.html'

    def get_object(self, queryset=None):
        obj = super(OutcomeTransfers, self).get_object(queryset)

        if not obj.is_owner(self.request.user.citizen):
            raise Http404

        return obj

    def _get_queryset(self):
        return self.model.objects.get(id=self.kwargs['pk']).outcome_transfers.all()

    def get_context_data(self, **kwargs):
        data = super(OutcomeTransfers, self).get_context_data()
        queryset = self._get_queryset()
        if 'sort' in self.request.GET:
            queryset = queryset.order_by(self.request.GET['sort'])
        data['table'] = MoneyTransferTable(queryset)
        return data


class IncomeTransfers(DetailView):
    model = BankAccount
    template_name = 'bank/report.html'

    def get_object(self, queryset=None):
        obj = super(IncomeTransfers, self).get_object(queryset)

        if not obj.is_owner(self.request.user.citizen):
            raise Http404

        return obj

    def _get_queryset(self):
        return self.model.objects.get(id=self.kwargs['pk']).income_transfers.all()

    def get_context_data(self, **kwargs):
        data = super(IncomeTransfers, self).get_context_data()
        queryset = self._get_queryset()
        if 'sort' in self.request.GET:
            queryset = queryset.order_by(self.request.GET['sort'])
        data['table'] = MoneyTransferTable(queryset)
        return data

models.py

代码语言:javascript
复制
class RuleQuerySet(models.QuerySet):

    def on_date(self, _date):
        return self.get(start_date__lte=_date, end_date__gte=_date)


class TransferQuerySet(models.QuerySet):

    def on_date(self, _date):
        return self.filter(when__lt=_date + timedelta(days=1), when__gte=_date)


class Rule(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()

    deposit_percent = models.DecimalField(max_digits=10, decimal_places=3)
    credit_percent = models.DecimalField(max_digits=10, decimal_places=3)

    objects = RuleQuerySet.as_manager()


class BankAccount(models.Model):

    organization = models.OneToOneField(Organization, related_name='bank_account', blank=True, null=True)
    citizen = models.OneToOneField(Citizen, related_name='bank_account', blank=True, null=True)

    DEBET = 1
    CREDIT = 2

    method = models.PositiveSmallIntegerField(choices=(
        (DEBET, 'Дебет'),
        (CREDIT, 'Кредит'),
    ))

    when_opened = models.DateField()

    @property
    def owner(self):
        if self.is_legal():
            return self.organization
        return self.citizen

    def __unicode__(self):
        prefix = "L" if self.is_legal() else "N"
        prefix += "D" if self.method == self.DEBET else "C"
        public_id = str(self.id).zfill(4)
        return "%s (#%s)" % (self.owner, prefix + public_id)

    def is_legal(self):
        if self.organization and self.citizen:
            raise ValueError("Account is legal and natural at the same time. Make it ")
        return self.organization is not None

    def is_owner(self, citizen):
        return (self.citizen and self.citizen.id == citizen.id) or \
               (self.organization and self.organization.owners.all().filter(id=citizen.id).count())

    @property
    def balance(self):
        today = timezone.localtime(timezone.now()).date()
        if self.method == self.CREDIT:
            percent = Rule.objects.on_date(self.when_opened).credit_percent * decimal.Decimal(str(0.01))
        else:
            percent = Rule.objects.on_date(self.when_opened).deposit_percent * decimal.Decimal(str(0.01))
        result = 0
        current_date = self.when_opened
        while current_date <= today:
            dd = self.sum_total_transfers_on_date(current_date)
            result += dd
            if current_date < today:
                result *= 1 + percent
            current_date += timedelta(days=1)
        return result

    def sum_total_transfers_on_date(self, _date):
        result = 0
        result -= sum(x.total for x in self.cashing_set.on_date(_date))
        result -= sum(x.total for x in self.outcome_transfers.on_date(_date))
        result += sum(x.total for x in self.deposit_set.all().on_date(_date))
        result += sum(x.total for x in self.income_transfers.on_date(_date))
        return result


class MoneyTransfer(models.Model):
    sender = models.ForeignKey(BankAccount, related_name='outcome_transfers', verbose_name="Отправитель")
    receiver = models.ForeignKey(BankAccount, related_name='income_transfers', verbose_name="Получатель")
    when = models.DateTimeField(verbose_name="Время")
    total = models.DecimalField(max_digits=10, decimal_places=3, verbose_name="Сумма")
    comment = models.CharField(max_length=255, verbose_name="Комментарий")

    objects = TransferQuerySet.as_manager()


class Deposit(models.Model):
    account = models.ForeignKey(BankAccount)
    total = models.DecimalField(max_digits=10, decimal_places=3)
    banker = models.ForeignKey(Citizen)
    when = models.DateTimeField()

    objects = TransferQuerySet.as_manager()


class Cashing(models.Model):
    account = models.ForeignKey(BankAccount)
    total = models.DecimalField(max_digits=10, decimal_places=3)
    banker = models.ForeignKey(Citizen)
    when = models.DateTimeField()

    objects = TransferQuerySet.as_manager()
EN

回答 2

Code Review用户

回答已采纳

发布于 2016-01-13 19:50:39

  1. 在尝试查找是否存在与该筛选器匹配的对象时,请使用exists而不是count。self.organization.owners.filter(id=citizen.id).exists()
  2. 使用泛型外键而不是多个one-to-one关系。将组织和公民更改为一个通用的字段owner。上面有很多很好的例子,但是这里只有一个。
  3. 为什么使用CashingDepositMoneyTransfer作为单独的模型而不是一个带有type选项字段的模型Transaction?然后你就可以根据type类事务(models.Model)来计算:存款=1现金=2转移=3选择=(存款,‘存款’),(现金,‘现金’),(转账,转移)帐户= models.ForeignKey(BankAccount)总计= models.DecimalField(max_digits=10,( decimal_places=3)银行家=models.ForeignKey(公民) when = models.DateTimeField() type = models.IntegerField(choices=CHOICES) objects = TransferQuerySet.as_manager()
  4. 实现一个IsOwnerMixin,该方法具有一个get_queryset方法,该方法对所有者等于用户所有者的对象执行筛选。def get_queryset(self):default_qs = super(IsOwnerMixin,self).get_queryset() qs =default_qs返回qs --当然,这是一个过于简化的例子。您需要添加逻辑,以确定用户是否是组织的一部分,如果是,则根据组织id而不是公民id进行筛选。下面是我们的代码库中的一个示例,它只返回属于用户company'. This isn't all of the code but it is enough to get you started. class CompanyRelationMixin(object): company\_path = "company" force\_company\_only = False def is\_customer(self): return not self.request.user.is\_superuser and self.request.user.user\_profile.company def company\_fk\_allows\_null(self, qs): ## Figures out which field on model relates to公司的对象,这些细节对您的情况不重要: default_qs =超级(CompanyRelationMixin,(.get_queryset() request = self.request options = '‘如果不是self.force_company_only: allows_none = self.company_fk_allows_null(default_qs)如果allows_none: options = Q(**{self.company_path: None})如果self.is_customer():company= request.user.user_profile.company.pk if options: options |= Q(**{self.company_path: company}) if: options = Q(**{self.company_path: company}) elif request.user.is_superuser: try: request.QUERY_PARAMS“公司”除了KeyError:返回default_qs options = Q(**{self.company_path: request.QUERY_PARAMS“公司”}) options |= Q(**{self.company_path: None})如果选项: default_qs =default_qs.filter(选项)返回default_qs,这些对于开始增长的视图数量非常有用。这消除了您的视图中的大量冗余。

我会在以后添加更多,如果我能想到任何其他的东西,将有助于干燥这一点。

票数 4
EN

Code Review用户

发布于 2016-01-13 20:40:02

您的模型

我们已经在你的另一个问题中讨论了其中的大部分内容,但是您已经做了一些更改,我可能也会回顾一下。

创建一个筛选器,用于获取公民

拥有的银行帐户

您已经有了过滤RuleTransfer对象的查询集,但是BankAccount中有可以分组的逻辑。具体来说,确定某人是否是银行帐户所有者的逻辑可以在查询中完成。

代码语言:javascript
复制
class BankQuerySet(models.QuerySet):

    def owned_by(self, citizen):
        from django.db.models import Q

        organizations_owned_by_citizen = Organization.objects.filter(
            owners__in=[citizen]
        ).distinct()

        return self.filter(
            (Q(owner_id=citizen.id) & Q(owner_ct=Citizen)) |
            (Q(owner_id__in=organizations_owned_by_citizen) & Q(owner_ct=Organization))
        )

使用一个通用外键而不是两个可空的外键

现在,可以在没有公民或组织的情况下创建BankAccount。使用通用外键,BankAccount将使用一组公共字段指向所有者(组织或公民),这就消除了对owner属性的需求。将来,您还可以作为银行帐户的所有者添加其他模型,而无需对代码进行重大更改。这也减少了is_legal所需的逻辑,这听起来可能更好地命名为is_owned_by_organization,但我可能遗漏了一些东西。

代码语言:javascript
复制
owner = GenericForeignKey(
    'object_id',
    'object_ct',
)
owner_id = models.PositiveIntegerField()
owner_ct = models.ForeignKey("contenttypes.ContentType")

def is_legal(self):
    return self.owner_ct is Organization

def is_owner(self, citizen):
    if self.owner is Citizen:
        return self.owner_id == citizen.id

    return self.owner.owners.filter(id=citizen.id).exists()

在过滤器

中使用"0.01"而不是str(0.01)

这是一个小问题,但关键是你不应该提前计算你已经知道的值。str(0.01)应该与0.01相同,它避免了Decimal对象实际上没有0.01的预期值的风险。

代码语言:javascript
复制
if self.method == self.CREDIT:
    percent = Rule.objects.on_date(self.when_opened).credit_percent * decimal.Decimal("0.01")
else:
    percent = Rule.objects.on_date(self.when_opened).deposit_percent * decimal.Decimal("0.01")

在需要计数时使用QuerySet.count(),在需要布尔值

时使用QuerySet.exists()

确定某人是否为组织所有者的逻辑返回查询集的.count(),即使该方法应该返回布尔值。.exists()方法将对数据库提出更轻的请求,数据库只检查一行是否存在,而不是获取查询集返回的所有行的计数。

您的视图

这是你问题的要点,也有一些东西可以通过它们来改进。

限制查询集,而不是重写get_object

现在,所有视图都共享同一个get_object方法,该方法调用get_queryset并检查返回的BankAccount是否有效。您可以通过不从get_queryset返回无效对象来消除对此的需求,这将引发当前手动引发的404错误。

代码语言:javascript
复制
def get_queryset(self):
    return BankAccount.objects.owned_by(self.request.user.citizen)

get_queryset/get_object使用混合器的

您目前正在所有视图中重复您的get_object方法,尽管逻辑是相同的。Python支持混合器,这允许您对公共代码进行分组,并跨多个类使用它,例如get_object/get_queryset方法。这样,如果你将来需要做出改变,你只需要在一个地方做出改变。 class BankObjectMixin(object): def get_queryset(self): return BankAccount.objects.owned_by(self.request.user.citizen) class AccountDetail(BankObjectMixin, DetailView): class SendTransfer(BankObjectMixin, SingleObjectMixin, FormView): class OutcomeTransfers(BankObjectMixin, DetailView): class IncomeTransfers(BankObjectMixin, DetailView): # ...

避免覆盖dispatch,除非您绝对需要

通常,这表明您正在做错误的事情,在本例中,为所有方法设置self.object。为get_queryset建议的更改将绕过您可能面临的问题,您可以在需要所请求的对象时继续调用self.get_object()

代码语言:javascript
复制
BankAccount.objects.get(id=self.kwargs['pk'])

会变成

代码语言:javascript
复制
self.get_object()
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/116660

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档