摘要
现实生活中的django应用程序通常具有一些需求,而这些需求通常无法通过互联网上的大多数django教程很好地宣传。 在这里,我分享了一些技巧,可以帮助您构建更好的Django应用。
使用UUID代替ID来引用对象。
举例来说,假设您要构建的应用程序将破坏金融市场,也许是比特币,或者您将要修复银行系统(请这样做)。
您的用户登录,进行交易并看到类似以下内容:
1 |
http://financeapp.com/transaction/37/ |
用户将意识到到目前为止,在您的应用程序上仅进行了37笔交易,说实话,他可能不会在意这一点,但是根据您的听众的不同,隐藏此信息可能会有用。 这也是您可能希望对当前竞争对手隐瞒的信息,或者如果他们关注您,甚至希望对新闻界隐瞒。
隐藏这些顺序ID的标准方法是将它们替换为UUID,如下所示:
1 |
http://financeapp.com/transaction/e12616c3-d81d-47a4-b01a-30d785be5251/ |
在Django中实现该功能所需的代码非常简单,几乎可以自我解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# urls.py urlpatterns = [ path('admin/', admin.site.urls), path('transaction/<uuid:slug>/', TransactionDetailView.as_view()), path('transaction/<int:pk>/', TransactionDetailView.as_view()), ] # views.py class TransactionDetailView(DetailView): model = Transaction # models.py class Transaction(models.Model): slug = models.UUIDField(default=uuid.uuid4, db_index=True) amount = models.IntegerField(default=1000) |
一般而言,接受pk作为参数的基于类的视图可以改为接受名为slug的参数,并且该slug参数可以是所需的任何任意类型(通常是UUID类型)。[1]
正如您可能已经注意到的,我也通过常规ID保留了访问权限,但是只是为了让Django接受两种方式,您绝对不希望在应用程序中保留两种访问方式。
将编辑/更新/删除限制为对象的所有者。
在对象级别访问权限中,应在新应用程序中解决的一个基本安全性问题通常从一个基本方面出发,即只允许用户查看,编辑和删除他是合法所有者的对象。这些年来,我似乎对如何在CBV上做到这一点有很多建议,他们总是感到奇怪,原因是,尽管从技术上来说是正确的,但他们却忽略了Django有一种更简单的方法来实现这种行为的事实。
首先,我们只想显示属于用户的事务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class TransactionListView(ListView): models = Transaction def get_queryset(): return Transaction.objects.filter(user=self.request.user) Straight forward, simple, elegant and well know... Now let's do the same for delete, hold your jaw: class TransactionDeleteView(DeleteView): models = Transaction def get_queryset(): return Transaction.objects.filter(user=self.request.user) And again for update: class TransactionUpdateView(UpdateView): models = Transaction def get_queryset(): return Transaction.objects.filter(user=self.request.user) And again for Detail: class TransactionDetailView(DetailView): models = Transaction def get_queryset(): return Transaction.objects.filter(user=self.request.user) |
这就对了!您不必怪异地覆盖调度,get_object或您在互联网上发现的任何其他奇怪的方式…
我花了一些时间认识到…我想,每当我在对象级访问视图中看到get_queryset时,我就会想:“这很奇怪,为什么视图中有一个只能用于单个对象的queryset方法” ,但事实是,此抽象将转换为纯SQL,在SQL中,您没有get_object或get_queryset,它将始终是SELECT语句。
此处的get_queryset用作get_object的前置过滤器,并确保仅在查询匹配时才执行操作[2]。如果仍然看起来很怪异,我向您保证这种感觉很快就会消失。如果不是这样,至少怪异性将生活在框架的抽象中,而不是没人同意的怪异替代。
从CBV方法渲染变量。
我不到两年就学到了这一功能……我不知道它实际上是django的一项相对较新的功能,还是只是隐藏在文档中,但这对我来说是一个改变游戏规则的人。
每当您需要向模板公开一些变量时,最常见的方法就是向get_context_data添加一些内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class TransactionDeleteView(DeleteView): models = Transaction def get_queryset(): return Transaction.objects.filter(user=self.request.user) def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) data['something_you_want_to_display_on_delete_template'] = '42' return data There is a simpler way of doing that, a way that can help you have more organized code. Instead of packing a lot of variables and calls to the get_context_data you can create methods and access them in templates, like this: class TransactionDeleteView(DeleteView): models = Transaction def get_queryset(): return Transaction.objects.filter(user=self.request.user) def something_you_want_to_display_on_delete_template(self) return "42" In your template: <h1> Are you sure about deleting {{ object }} </h1> <form> <buttom></button> </form> <h2> Here I will display other relevant information to let you make a concious choice</h2> <p> {{ view.something_you_want_to_display_on_delete_template }} </p> |
缓存昂贵的计算,每个请求需要多次访问。
Django有一个内置的装饰器@cached_property用于缓存短暂存在的属性,缓存将与持有该属性的实例保持相同的时间。 实例死亡后,缓存就会死亡。 在模板中两次访问相同的资源时,通常需要此功能。
您可以将装饰器放置在任何级别,它可以位于view属性上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# views.py class TransactionDetailView(DetailView): models = Transaction @cached_property def some_expensive_computation_from_view() return magic_that_will_return_42() Or even better(?), it can save you some queries to database: # models.py class Transaction(models.Model): slug = models.UUIDField(default=uuid.uuid4, db_index=True) amount = models.IntegerField(default=1000) @cached_property def some_expensive_queryset_from_model() return ExpensiveQueryset.filter().all() |
坦白说,在模型级别使用它可能会导致您打算在代码的其他部分重用该方法并在不应该使用该方法时对其进行缓存。 只要有可能,就将@cached_property保持在View级别。
为了完整起见,我们来看一个假的@cached_property示例,第一个示例将花费4秒钟来呈现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# views.py class TransactionDetailView(DetailView): models = Transaction def correct_answer() time.sleep(2) return 42 # transaction_detail.html {% if view.correct_answer == 42 %} {{ view.correct_answer }} {% endif %} Now with the decorator it will take 2 seconds to render: # views.py class TransactionDetailView(DetailView): models = Transaction @cached_property def correct_answer() time.sleep(2) return 42 # transaction_detail.html {% if view.correct_answer == 42 %} {{ view.correct_answer }} {% endif %} |
即使我们在模板中两次调用view.correct_answer,它实际上也只会在条件评估期间执行一次。 打印结果的第二个调用不会发生,只会返回缓存的结果。
最后的话
此处介绍的每个技巧都可以带来一些折衷和特殊性。 尽管在某些情况下不使用这些功能可能是一个明智的选择,但是尝试从头开始复制其中一些功能可能会导致您使用更差的代码,或者至少变得更加费力。