Le goût de Django nous excite et attire

Mangez des cactus

Cela fait plusieurs semaines depuis la sortie officielle de la version 3 de Django. J'ai travaillé avec cette version avant même la publication de la sortie officielle et, malheureusement, j'ai remarqué que le développement de Django s'est considérablement ralenti. La version 1.3 diffère parfois de 1.7, mais la version 3 contient des modifications cosmétiques de la branche 2 et pas plus.

Mon projet winePad a commencé avec Django 1.3, et maintenant il a redéfini environ 12% du code interne de Django.

En voyant le code de la nouvelle version, je comprends que les modifications que moi ou mes collègues avons apportées en travaillant avec les versions précédentes iront plus loin. Mais en regardant la feuille de route et les changements lents dans le référentiel officiel, nous n'avons pas à attendre que les erreurs soient corrigées dans les futures versions.

Voici ce que je veux parler de travailler sur les bugs:

Obtenir la méthode


Peu de gens se rendent compte qu'il y avait une erreur dans la méthode standard get django dès le début. La méthode get doit soit vous renvoyer un objet, soit avertir que plusieurs objets ont été trouvés, soit signaler qu'il n'y a pas d'objets.

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

La ligne 9 reçoit des données sur TOUS les enregistrements spécifiés dans le jeu de requêtes et les traduit en un ensemble d'objets. Il y a un avertissement à ce sujet dans la documentation.

Avant la version 3, il n'y avait pas de limite sur le nombre d'objets demandés. Cela signifie que vous avez reçu absolument toutes les données et les avez transformées en objets, avant de donner un avertissement qu'il y a beaucoup d'objets.

En conséquence, vous pouvez obtenir plusieurs millions d'objets en mémoire, seulement pour découvrir que plus d'un objet a été trouvé. Maintenant, il y a les lignes 5,7,8. Et maintenant, vous obtenez fièrement seulement MAX_GET_RESULTS = 21 objets avant de savoir qu'il y a plus de 1 objets.
Avec un "__init__" lourd, le retard sera important. Comment traiter:
Substituez MAX_GET_RESULTS dans django.db.models.query.py
remplacer GET ou avant d'appeler GET, utilisez:

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

Méthode __Init__ des modèles Django


La déclaration dans le code de la méthode intégrée __init__ n'est pas entièrement claire

 _setattr=setattr 

c'est probablement pour pseudo-accélérer le code en transférant une référence de fonction à un dictionnaire local, mais ce n'est pas à ce sujet. Il y a plusieurs problèmes:

1. Si vous passez des paires attribut = valeur supplémentaires dans le modèle __init__, vous obtiendrez "un argument de mot clé inattendu".

Dans ce cas, je suggère de ne pas surcharger la méthode __init__, mais d'ajouter des attributs après l'initialisation:

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

2. Le nouveau Django a ajouté la possibilité de redéfinir les descripteurs sur n'importe quel champ du modèle (Field.descriptor_class). Mais aucun descripteur ne sait si l'objet est initialisé ou non. Cela est nécessaire, par exemple, si le descripteur utilisera des données d'objets prefetch_related qui n'apparaîtront qu'après l'initialisation de l'objet principal.

Je n'aime pas utiliser le signal de fin d'initialisation, car il peut y avoir beaucoup d'abonnés.

Dans ce cas, je n'ai rien trouvé de plus intelligent que de remplacer __init__ et d'ajouter un attribut pour terminer l'initialisation.

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

Querysets


Le EmptyQuerySet / DateQuerySet codé en dur a été supprimé, il est déjà bon. Cependant, la situation avec queryset en tant que gestionnaire que je n'aime pas idéologiquement.

Si je veux remplacer la classe QuerySet créée par les gestionnaires, j'ajoute l'attribut _queryset_class

 class MyManager(models.Manager): _queryset_class = MyQuerySet 

Attention, pour les anciennes versions cela ne fonctionne pas, par exemple, vous pouvez le faire:

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

panneau d'administration inlineFormset


Il y a plusieurs problèmes:

1. Il n'est pas possible d'afficher les entrées standard inlineFormset qui ne communiquent pas directement avec l'objet de formulaire principal. Par exemple: un formulaire pour modifier les prix des produits. Au milieu se trouve la forme statistique de référence tabulaire en ligne des prix d'achat en gros pour les marchandises «similaires».

Il est résolu en remplaçant la méthode du modèle en ligne get_formset et en créant votre propre MyFormSet hérité de BaseInlineFormSet

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

2. Si vous modifiez un objet avec un inlineformset dans le panneau d'administration, et à ce moment quelqu'un supprimera l'un des enregistrements d'objet à l'intérieur de l'inlineformset via un autre mécanisme, vous recevrez une erreur et vous ne pourrez pas enregistrer l'objet. Uniquement via kopy paste dans une nouvelle fenêtre de navigateur.

Je n'ai trouvé qu'une seule solution jusqu'à présent - n'utilisez pas inlineformset.

Panneau d'administration


Le «dispositif tueur» de Django, est le plus grand cactus du projet:

1. L'action "Supprimer des objets" dans les administrateurs de modèle est visible par défaut, peu importe que l'utilisateur ait le droit de supprimer ou non.

Il est résolu en désactivant cette action par défaut:

 admin.site.disable_action('delete_selected') 

2. La création de droits d'utilisateur supplémentaires à partir du panneau d'administration ne sera pas possible tant que vous n'aurez pas activé l'administrateur du modèle Autorisations:

 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. Hélas, le droit d'accéder uniquement à certains objets dans Django n'existe pas.

Cela peut être résolu en écrivant une entrée dans djangoAdminLog avec un indicateur spécial.
Et puis vérifiez le drapeau:

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

4. Si vous créez des actions d'administrateur comme décrit dans la documentation, n'oubliez pas qu'elles ne sont pas enregistrées automatiquement dans djangoAdminLog.

5. Un autre inconvénient de cette partie de la documentation est que tous les exemples concernent uniquement des fonctions. Mais qu'en est-il de GCBV? Dans mes projets, toutes les actions des administrateurs de modèles sont transférées vers GCBV. Dépôt.

La connexion d'action est standard:

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

ContentType - Registre de modèles Django


50% génie / 50% matité.
Aucun des modèles n'a accès au registre de modèles par défaut.
Dans nos projets, il est résolu en ajoutant un mixin à toutes les 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 

maintenant nous pouvons appeler obj.ct () si nécessaire.

Usermodel


La possibilité de remplacer le modèle utilisateur est apparue dans la version 1.5.

Mais pour la version 3, ils n'ont pas corrigé model = User dans le standard UserCreationForm / UserChangeForm.
Il est résolu:

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

Système de traduction


Les balises visibles par l'utilisateur sont balisées

 {% trans %} 

ou en code via

 gettext_lazy 

Cependant, il n'y a pas de gestion de ces ressources dans le panneau d'administration. Généralement.

Il existe des solutions externes, elles fonctionnent toutes en quelque sorte.

Par exemple, Rosetta perd systématiquement des textes, et l'interface fonctionne en buggy. Il n'y a aucune vérification des droits d'accès aux traductions nulle part. Des makemessages / compilemessages systématiques sont nécessaires pour fonctionner ...

Dans winePad, les balises trans, blocktrans et gettext_lazy ont été redéfinies et nous avons commencé à recevoir des textes du cache. S'il n'y a pas de cache, la requête mise en cache de la base get_or_create nous a également sauvés de makemessages.

Le sujet du multilinguisme est généralement compliqué. La solution intégrée de Django ne fonctionne que pour les textes statiques. Mais il faut encore traduire ces modèles. J'ai essayé à ma façon de résoudre le problème de la traduction de textes dynamiques dans le projet django-TOF , où j'ai combiné les capacités de traduction de modèles et Parler / Hvad. Quelqu'un sera probablement intéressé à jeter un œil.

Pour l'instant, je vais arrêter le récit, car l'article de correction de bogue de Django pourrait facilement se transformer en une longue lecture.

Veuillez me dire comment vous améliorez votre Django. S'il y a une suite, je systématise les idées qui ont surgi.

ps Certains codes sont écrits dans l'ancienne notation, j'espère pour la compréhension, le temps ou le personnel de refactorisation n'est pas toujours trouvé.

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


All Articles