我如何能够编写/Djangoize/干燥我的Django代码?
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 dataclass 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()发布于 2016-01-13 19:50:39
exists而不是count。self.organization.owners.filter(id=citizen.id).exists()one-to-one关系。将组织和公民更改为一个通用的字段owner。上面有很多很好的例子,但是这里只有一个。Cashing、Deposit和MoneyTransfer作为单独的模型而不是一个带有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()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,这些对于开始增长的视图数量非常有用。这消除了您的视图中的大量冗余。我会在以后添加更多,如果我能想到任何其他的东西,将有助于干燥这一点。
发布于 2016-01-13 20:40:02
我们已经在你的另一个问题中讨论了其中的大部分内容,但是您已经做了一些更改,我可能也会回顾一下。
拥有的银行帐户
您已经有了过滤Rule和Transfer对象的查询集,但是BankAccount中有可以分组的逻辑。具体来说,确定某人是否是银行帐户所有者的逻辑可以在查询中完成。
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,但我可能遗漏了一些东西。
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的预期值的风险。
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错误。
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()。
BankAccount.objects.get(id=self.kwargs['pk'])会变成
self.get_object()https://codereview.stackexchange.com/questions/116660
复制相似问题