O gosto do Django nos excita e atrai

Coma cacto

Faz várias semanas desde o lançamento oficial da versão 3 do Django. Eu trabalhei com esta versão mesmo antes da publicação do lançamento oficial e, infelizmente, notei que o desenvolvimento do Django diminuiu significativamente. A versão 1.3 difere de 1.7 às vezes, mas a versão 3 contém alterações cosméticas na ramificação 2 e não mais.

Meu projeto winePad começou com o Django 1.3 e agora redefiniu cerca de 12% do código interno do Django.

Vendo o código da nova versão, entendo que as edições que eu ou meus colegas fizemos ao trabalhar com versões anteriores irão além. Mas, observando o roteiro e as lentas mudanças no repositório oficial, não precisamos esperar que os erros sejam corrigidos em versões futuras.

Isto é o que eu quero falar sobre como trabalhar em bugs:

Método Get


Poucas pessoas percebem que houve um erro no método get django padrão desde o início. O método get deve retornar um objeto para você ou avisar que vários objetos foram encontrados ou relatar que não há objetos.

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() 

A linha 9 recebe dados em TODOS os registros especificados no conjunto de consultas e os converte em um conjunto de objetos. Há um aviso sobre isso na documentação.

Antes da versão 3, não havia limite no número de objetos solicitados. Isso significa que você recebeu absolutamente todos os dados e os transformou em objetos, antes de emitir um aviso de que existem muitos objetos.

Como resultado, você pode obter vários milhões de objetos na memória, apenas para descobrir que mais de um objeto foi encontrado. Agora existem linhas 5,7,8. E agora você orgulhosamente obtém apenas MAX_GET_RESULTS = 21 objetos antes de saber que existem mais de 1 objeto.
Com "__init__" pesado, o atraso será significativo. Como tratar:
Substituir MAX_GET_RESULTS em django.db.models.query.py
substituir GET ou antes de chamar GET use:

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

Método __Init__ dos modelos Django


A declaração no código do método interno __init__ não está totalmente clara

 _setattr=setattr 

isso provavelmente é para pseudo-proteger o código transferindo a referência de função para o dicionário local, mas não é sobre isso. Existem vários problemas:

1. Se você passar pares adicionais attribute = value no modelo __init__, receberá "um argumento inesperado da palavra-chave".

Nesse caso, sugiro não sobrecarregar o método __init__, mas adicionar atributos após a inicialização:

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

2. O novo Django adicionou a capacidade de redefinir descritores em qualquer campo do modelo (Field.descriptor_class). Mas nenhum descritor sabe se o objeto foi inicializado ou não. Isso é necessário, por exemplo, se o descritor usar dados de objetos relacionados a pré-busca que aparecerão somente após a inicialização do objeto principal.

Não gosto de usar o sinal do final da inicialização, porque pode haver muitos assinantes.

Nesse caso, não criei nada mais inteligente do que substituir __init__ e adicionar um atributo para finalizar a inicialização.

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

Conjuntos de consultas


O EmptyQuerySet / DateQuerySet codificado foi removido, já é bom. No entanto, a situação com o queryset como gerente de que eu ideologicamente não gosta.

Se quiser substituir a classe QuerySet criada pelos gerentes, adiciono o atributo _queryset_class

 class MyManager(models.Manager): _queryset_class = MyQuerySet 

Atenção, para versões mais antigas, isso não funciona, por exemplo, você pode fazer o seguinte:

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

painel de administração do inlineFormset


Existem vários problemas:

1. Não é possível exibir entradas inlineFormset padrão que não se comunicam diretamente com o objeto de formulário principal. Por exemplo: um formulário para editar preços de produtos. No meio, encontra-se a forma estatística de referência Tabularinline, de preços de compra no atacado de mercadorias "similares".

Isso é resolvido substituindo o método do modelo inline get_formset e criando seu próprio MyFormSet herdado de BaseInlineFormSet

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

2. Se você estiver editando um objeto com um inlineformset no painel do administrador e, nesse momento, alguém excluirá um dos registros de objeto dentro do inlineformset por outro mecanismo, você receberá um erro e não poderá salvar o objeto. Somente através da pasta kopy em uma nova janela do navegador.

Encontrei apenas uma solução até agora - não use o inlineformset.

Painel do administrador


O "recurso matador" do Django, é o maior cacto do projeto:

1. A ação "Excluir objetos" nos administradores de modelo é visível por padrão, não importa se o usuário tem direitos para excluir ou não.

É resolvido desativando esta ação por padrão:

 admin.site.disable_action('delete_selected') 

2. A criação de direitos de usuário adicionais no painel de administração não será possível até que você ative o administrador do modelo. Permissões:

 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. Infelizmente, o direito de acessar apenas certos objetos no Django não existe.

Isso pode ser resolvido escrevendo uma entrada no djangoAdminLog com um sinalizador especial.
E, em seguida, verifique a bandeira:

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

4. Se você criar ações de administrador, conforme descrito na documentação, lembre-se de que elas não são registradas automaticamente no djangoAdminLog.

5. Outra desvantagem desta parte da documentação é que todos os exemplos são apenas de funções. Mas e o GCBV? Nos meus projetos, todas as ações dos administradores de modelo são transferidas para o GCBV. Repositório.

A conexão de ação é padrão:

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

ContentType - registro do modelo django


50% de gênio / 50% de embotamento.
Nenhum dos modelos tem acesso ao registro de modelo padrão.
Em nossos projetos, isso é resolvido adicionando um mixin a todas as classes:

 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 

agora podemos chamar obj.ct () se necessário.

Usermodel


A capacidade de substituir o modelo do usuário apareceu na versão 1.5.

Mas para a versão 3, eles não corrigiram model = User no UserCreationForm / UserChangeForm padrão.
Está resolvido:

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

Sistema de tradução


Tags visíveis para o usuário são marcadas

 {% trans %} 

ou no código através

 gettext_lazy 

No entanto, não há gerenciamento desses recursos no painel do administrador. Geralmente.

Existem soluções externas, todas elas funcionam de alguma forma.

Por exemplo, o Rosetta perde sistematicamente os textos e a interface está funcionando de buggy. Não há verificação de direitos de acesso a traduções em nenhum lugar. São necessárias mensagens sistemáticas de composição / compilação para ...

No winePad, as tags trans, blocktrans e gettext_lazy foram redefinidas e começamos a receber textos do cache. Se não houver cache, a solicitação em cache da base get_or_create nos salvou também de makemessages.

O tópico multilinguismo é geralmente complicado. A solução interna do Django funciona apenas para textos estáticos. Mas ainda é necessário traduzir esses modelos. Tentei, à minha maneira, resolver a questão da tradução de textos dinâmicos no projeto django-TOF , onde combinei os recursos de tradução de modelos e Parler / Hvad. Provavelmente alguém estará interessado em espreitar.

Por enquanto, vou parar a narrativa, pois o artigo sobre correção de erros do Django pode facilmente se transformar em um longo caminho.

Por favor me diga como você está melhorando seu Django. Se houver uma continuação, sistematizo as idéias que surgiram.

ps Alguns códigos estão escritos na antiga notação, espero que sejam entendidos, nem sempre o tempo ou a equipe de refatoração.

Source: https://habr.com/ru/post/pt481042/


All Articles