我们Django的口味激发并吸引

吃仙人掌

自Django第3版正式发布以来已经过去了几周。 甚至在正式版本发布之前,我就使用该版本。不幸的是,我注意到Django的开发速度大大降低了。 版本1.3有时与1.7不同,但是版本3包含对分支2的外观更改,仅此而已。

我的winePad项目始于Django 1.3,到目前为止,它已经重新定义了Django内部代码的12%。

看到新版本的代码后,我了解到我或我的同事在使用旧版本时所做的编辑将会更进一步。 并且查看路线图和官方存储库中的缓慢更改,您不必等待将来的版本中纠正错误。

这就是我要谈论的关于错误的工作:

获取方法


很少有人意识到从一开始标准get django方法就存在错误。 get方法应该将一个对象返回给您,或者警告已找到多个对象,或者报告没有对象。

1 def get(self, *args, **kwargs): 2 clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs) 3 if self.query.can_filter() and not self.query.distinct_fields: 4 clone = clone.order_by() 5 limit = None 6 if not clone.query.select_for_update or connections[clone.db].features.supports_select_for_update_with_limit: 7 limit = MAX_GET_RESULTS 8 clone.query.set_limits(high=limit) 9 num = len(clone) 10 if num == 1: 11 return clone._result_cache[0] 12 if not num: 13 raise self.model.DoesNotExist() 14 raise self.model.MultipleObjectsReturned() 

第9行接收queryset中指定的所有记录上的数据,并将它们转换为一组对象。 文档中对此有警告。

在版本3之前,请求的对象数没有限制。 这意味着在发出警告说有很多对象之前,绝对要接收所有数据并将它们变成对象。

结果,您可以在内存中获得数百万个对象,而只是发现发现了多个对象。 现在有第5、7、8行。 现在,您自豪地只得到MAX_GET_RESULTS = 21个对象,然后才知道有1个以上的对象。
如果使用沉重的“ __init__”,则延迟将很明显。 如何治疗:
覆盖django.db.models.query.py中的MAX_GET_RESULTS
覆盖GET或在调用GET使用之前:

 vars(queryset.query).update({'high_mark':2, 'low_mark':0})  queryset.query.set_limits(0,2) 

Django模型的__Init__方法


__init__内置方法的代码中的声明并不十分清楚

 _setattr=setattr 

这可能是通过将函数引用转移到本地字典来伪加速代码的,但这不是关于此的。 有几个问题:

1.如果在__init__模型中传递了附加的attribute = value对,您将得到“得到一个意外的关键字参数”。

在这种情况下,我建议不要负担__init__方法,而要在初始化后添加属性:

 obj = MyClass() vars(obj).update({'attr':val, 'attr2':val2 ...}) 

2.新的Django添加了在模型的任何字段(Field.descriptor_class)上重新定义描述符的功能。 但是,没有一个描述符知道对象是否已初始化。 例如,如果描述符将使用来自prefetch_related对象的数据(仅在主对象初始化之后才出现),则这是必需的。

我不想使用信号来完成初始化,因为可能会有很多订阅者。

在这种情况下,我没有想出比重写__init__和添加属性结束初始化更聪明的方法了。

  def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._end_init = True 

查询集


硬编码的EmptyQuerySet / DateQuerySet已删除,已经很好了。 但是,从思想上讲,以queryset作为管理器的情况我并不喜欢。

如果要覆盖管理器创建的QuerySet类,请添加_queryset_class属性

 class MyManager(models.Manager): _queryset_class = MyQuerySet 

注意,对于较旧的版本,此功能不起作用,例如,您可以执行以下操作:

 class MyManager(models.Manager): def get_query_set(self): response = super(MyManager, self).get_query_set() response.__class__ = MyQuerySet return response 

inlineFormset管理面板


有几个问题:

1.无法显示与主表单对象没有直接连接的标准inlineFormset记录。 例如:用于编辑产品价格的表单。 在中间的是“类似”商品批发价格的参考统计表格形式。

通过重写get_formset内联模型方法并创建从BaseInlineFormSet继承的自己的MyFormSet可以解决此问题。

 def get_formset(self, request, obj=None, **kwargs): kwargs['formset'] = MyFormSet super().get_formset(request, obj, **kwargs) class MyFormSet(BaseInlineFormSet): pass 

2.如果您正在管理面板中使用inlineformset编辑一个对象,并且此时有人将通过另一种机制删除inlineformset内部的一个对象记录,则会收到错误消息,并且您将无法保存该对象。 仅通过kopy粘贴在新的浏览器窗口中。

到目前为止,我只找到一种解决方案-不要使用inlineformset。

管理面板


Django的“杀手级功能”是该项目最大的仙人掌:

1.默认情况下,模型管理员中的“删除对象”操作是可见的,用户是否有权删除都无关紧要。

通过默认禁用此操作可以解决此问题:

 admin.site.disable_action('delete_selected') 

2.在启用模型管理员权限之前,无法从管理面板创建其他用户权限:

 from django.contrib.auth.models import Permission class PermissionsAdmin(admin.ModelAdmin): search_fields = ('name', 'codename','content_type__app_label', 'content_type__model') list_display = ('name', 'codename',) actions = None admin.site.register(Permission, PermissionsAdmin) 

3. A,在Django中不存在仅访问某些对象的权利。

这可以通过使用特殊标志将条目写入djangoAdminLog来解决。
然后检查标志:

 user.logentry_set.filter(action_flag=ENABLED, .....).exists() 

4.如果按照文档中的说明创建管理员操作,请记住,它们不会自动记录在djangoAdminLog中。

5.该文档这部分的另一个缺点是所有示例都只针对函数。 但是GCBV呢? 在我的项目中,模型管理员的所有操作均已转移到GCBV。 仓库。

动作连接是标准的:

 class MyAdmin(admin.ModelAdmin): actions = (MyActionBasedOnActionView.as_view(),) 

ContentType-Django模型注册表


50%天才/ 50%迟钝。
没有一个模型可以访问默认模型注册表。
在我们的项目中,可以通过向所有类添加mixin来解决该问题:

 from django.contrib.contenttypes.models import ContentType class ExportMixin(object): @classmethod def ct(cls): if not hasattr(cls, '_ct'): cls._ct, create = ContentType.objects.get_or_create(**cls.get_app_model_dict()) if create: cls._ct.name = cls._ct.model._meta.verbose_name cls._ct.save() return cls._ct @classmethod def get_model_name(cls): if not hasattr(cls, '_model_name'): cls._model_name = cls.__name__.lower() return cls._model_name @classmethod def get_app_name(cls): if not hasattr(cls, '_app_name'): cls._app_name = cls._meta.app_label.lower() return cls._app_name @classmethod def get_app_model_dict(cls): if not hasattr(cls, '_format_kwargs'): cls._format_kwargs = {'app_label': cls.get_app_name(), 'model': cls.get_model_name()} return cls._format_kwargs 

现在我们可以根据需要调用obj.ct()。

用户模型


覆盖用户模型的功能出现在1.5版中。

但是对于版本3,他们没有在标准UserCreationForm / UserChangeForm中修复model = User。
解决了:

 from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import get_user_model class MyUserCreationForm(UserCreationForm): class Meta: model= get_user_model() 

翻译系统


用户可见的标签被标记

 {% trans %} 

或通过代码

 gettext_lazy 

但是,管理面板中没有对这些资源的管理。 一般而言。

有外部解决方案,它们都以某种方式起作用。

例如,罗塞塔(Rosetta)系统地丢失了文本,并且界面出现故障。 没有任何地方对翻译的访问权的验证。 需要系统的makemessages / compilationmess才能工作...

在winePad中,trans,blocktrans和gettext_lazy标签被重新定义,我们开始从缓存中接收文本。 如果没有缓存,则来自get_or_create基的缓存请求也从makemessages中保存了我们。

多种语言的话题通常很复杂。 Django的内置解决方案仅适用于静态文本。 但是仍然需要转换这些模型。 我以自己的方式尝试解决了django-TOF项目中翻译动态文本的问题,在该项目中,我结合了模型翻译和Parler / Hvad的功能。 可能有人会对偷看感兴趣。

现在,我将停止叙述,因为Django bug修复文章很容易变成经文。

请告诉我您如何改进Django。 如果有延续,我将所提出的想法系统化。

ps有些代码是用旧的符号编写的,希望能引起理解,并非总是能找到重构的时间或人员。

Source: https://habr.com/ru/post/zh-CN481042/


All Articles