Der Geschmack von uns Django regt an und zieht an

Essen Sie Kaktus

Seit der offiziellen Veröffentlichung von Version 3 von Django sind mehrere Wochen vergangen. Ich habe mit dieser Version bereits vor der Veröffentlichung des offiziellen Releases gearbeitet und leider festgestellt, dass sich die Entwicklung von Django erheblich verlangsamt hat. Version 1.3 unterscheidet sich zeitweise von Version 1.7, Version 3 enthält jedoch kosmetische Änderungen an Zweig 2 und nicht mehr.

Mein winePad-Projekt begann mit Django 1.3 und hat inzwischen etwa 12% des internen Codes von Django neu definiert.

Wenn ich den Code für die neue Version sehe, verstehe ich, dass die Änderungen, die ich oder meine Kollegen während der Arbeit mit früheren Versionen vorgenommen haben, noch weiter gehen werden. Wenn Sie sich die Roadmap und die schleppenden Änderungen im offiziellen Repository ansehen, müssen Sie nicht darauf warten, dass Fehler in zukünftigen Versionen behoben werden.

Dies ist, was ich über die Arbeit an Fehlern sprechen möchte:

Methode holen


Nur wenigen ist klar, dass es von Anfang an einen Fehler in der Standardmethode get django gab. Die get-Methode sollte entweder ein Objekt an Sie zurückgeben oder warnen, dass mehrere Objekte gefunden wurden, oder melden, dass keine Objekte vorhanden sind.

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

Zeile 9 empfängt Daten für ALLE Datensätze, die im Abfragesatz angegeben sind, und übersetzt sie in einen Satz von Objekten. In der Dokumentation wird diesbezüglich eine Warnung angezeigt.

Vor Version 3 war die Anzahl der angeforderten Objekte nicht begrenzt. Dies bedeutet, dass alle Daten empfangen und in Objekte umgewandelt werden, bevor eine Warnung ausgegeben wird, dass viele Objekte vorhanden sind.

Infolgedessen konnten Sie mehrere Millionen Objekte im Speicher abrufen, nur um festzustellen, dass mehr als 1 Objekt gefunden wurde. Jetzt gibt es die Zeilen 5,7,8. Und jetzt erhalten Sie stolz nur MAX_GET_RESULTS = 21 Objekte, bevor Sie wissen, dass es mehr als 1 Objekte gibt.
Bei starkem "__init__" ist die Verzögerung erheblich. Wie zu behandeln:
Überschreiben Sie MAX_GET_RESULTS in django.db.models.query.py
GET überschreiben oder vor dem Aufruf von GET verwenden:

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

__Init__-Methode von Django-Modellen


Die Deklaration im Code der integrierten Methode __init__ ist nicht ganz klar

 _setattr=setattr 

Dies dient wahrscheinlich dazu, den Code durch die Übertragung einer Funktionsreferenz in ein lokales Wörterbuch zu beschleunigen, aber darum geht es nicht. Es gibt verschiedene Probleme:

1. Wenn Sie im __init__-Modell zusätzliche Attribut = Wert-Paare übergeben, erhalten Sie "ein unerwartetes Schlüsselwortargument".

In diesem Fall empfehle ich, die Methode __init__ nicht zu belasten, sondern nach der Initialisierung Attribute hinzuzufügen:

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

2. Der neue Django bietet die Möglichkeit, Deskriptoren für jedes Feld des Modells neu zu definieren (Field.descriptor_class). Aber kein einziger Deskriptor weiß, ob das Objekt initialisiert ist oder nicht. Dies ist beispielsweise erforderlich, wenn der Deskriptor Daten von Prefetch-bezogenen Objekten verwendet, die erst nach der Initialisierung des Hauptobjekts angezeigt werden.

Ich möchte das Signal zum Ende der Initialisierung nicht verwenden, da es viele Abonnenten geben kann.

In diesem Fall habe ich nichts Klügeres gefunden, als __init__ zu überschreiben und ein Attribut hinzuzufügen, um die Initialisierung zu beenden.

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

Querysets


Das fest codierte EmptyQuerySet / DateQuerySet wurde entfernt. Es ist bereits in Ordnung. Allerdings gefällt mir die Situation mit queryset als Manager ideologisch nicht.

Wenn ich die von Managern erstellte QuerySet-Klasse überschreiben möchte, füge ich das _queryset_class-Attribut hinzu

 class MyManager(models.Manager): _queryset_class = MyQuerySet 

Achtung, bei älteren Versionen funktioniert dies nicht, zum Beispiel können Sie dies tun:

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

inlineFormset Admin Panel


Es gibt verschiedene Probleme:

1. Es ist nicht möglich, Standard-InlineFormset-Datensätze anzuzeigen, die keine direkte Verbindung zum Hauptformularobjekt haben. Zum Beispiel: Ein Formular zum Bearbeiten von Produktpreisen. In der Mitte liegt die tabellarische Referenzform der Großhandelspreise für „ähnliche“ Waren.

Dies wird gelöst, indem Sie die Inline-Modellmethode get_formset überschreiben und Ihr eigenes MyFormSet erstellen, das von BaseInlineFormSet geerbt wurde

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

2. Wenn Sie ein Objekt mit einem Inlineformset im Admin-Bereich bearbeiten und zu diesem Zeitpunkt jemand einen der Objektdatensätze im Inlineformset über einen anderen Mechanismus löscht, wird eine Fehlermeldung angezeigt und Sie können das Objekt nicht speichern. Nur durch Kopieren einfügen in einem neuen Browserfenster.

Ich habe bisher nur eine Lösung gefunden - benutze kein Inlineformset.

Admin Panel


Das "Killer-Feature" von Django ist der größte Kaktus des Projekts:

1. Die Aktion "Objekte löschen" in Modelladministratoren ist standardmäßig sichtbar, es spielt keine Rolle, ob der Benutzer zum Löschen berechtigt ist oder nicht.

Es wird gelöst, indem diese Aktion standardmäßig deaktiviert wird:

 admin.site.disable_action('delete_selected') 

2. Das Erstellen zusätzlicher Benutzerrechte über das Admin-Bedienfeld ist erst möglich, wenn Sie den Modelladministrator aktivieren. Berechtigungen:

 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. Das Recht, nur auf bestimmte Objekte in Django zuzugreifen, besteht leider nicht.

Dies kann gelöst werden, indem ein Eintrag in djangoAdminLog mit einem speziellen Flag geschrieben wird.
Und dann nach der Flagge suchen:

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

4. Wenn Sie Administratoraktionen wie in der Dokumentation beschrieben erstellen, beachten Sie, dass diese nicht automatisch in djangoAdminLog protokolliert werden.

5. Ein weiterer Nachteil dieses Teils der Dokumentation ist, dass sich alle Beispiele nur auf Funktionen beziehen. Aber was ist mit GCBV? In meinen Projekten werden alle Aktionen von Modelladministratoren an GCBV übertragen. Repository.

Die Aktionsverbindung ist Standard:

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

ContentType - Django Model Registry


50% Genialität / 50% Mattheit.
Keines der Modelle hat Zugriff auf die Standardmodellregistrierung.
In unseren Projekten wird dies durch Hinzufügen eines Mixins zu allen Klassen gelöst:

 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 

jetzt können wir bei Bedarf obj.ct () aufrufen.

Usermodel


Die Möglichkeit, das Benutzermodell zu überschreiben, wurde in Version 1.5 eingeführt.

In Version 3 wurde jedoch model = User in der Standard-UserCreationForm / UserChangeForm nicht behoben.
Es ist gelöst:

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

Übersetzungssystem


Tags, die für den Benutzer sichtbar sind, werden markiert

 {% trans %} 

oder in Code durch

 gettext_lazy 

Es gibt jedoch keine Verwaltung dieser Ressourcen im Admin-Bereich. Im Allgemeinen.

Es gibt externe Lösungen, die alle irgendwie funktionieren.

Zum Beispiel verliert Rosetta systematisch Texte und die Benutzeroberfläche funktioniert fehlerhaft. Es gibt nirgendwo eine Überprüfung der Zugriffsrechte auf Übersetzungen. Systematische Makemessages / Compilemessages sind erforderlich, um zu arbeiten ...

In winePad wurden die Tags trans, blocktrans und gettext_lazy neu definiert und es wurde begonnen, Texte aus dem Cache zu empfangen. Wenn es keinen Cache gibt, hat uns die zwischengespeicherte Anforderung von der get_or_create-Basis auch vor Makemessages bewahrt.

Das Thema Mehrsprachigkeit ist in der Regel kompliziert. Die integrierte Lösung von Django funktioniert nur für statische Texte. Diese Modelle müssen jedoch noch übersetzt werden. Ich habe auf meine eigene Weise versucht, das Problem der Übersetzung dynamischer Texte im Django-TOF- Projekt zu lösen, in dem ich die Fähigkeiten der Modellübersetzung und Parler / Hvad kombiniert habe. Wahrscheinlich wird sich jemand für das Spähen interessieren.

Im Moment werde ich die Erzählung stoppen, da der Artikel zur Fehlerbehebung in Django leicht zu einem Longread werden könnte.

Bitte sagen Sie mir, wie Sie Ihren Django verbessern. Wenn es eine Fortsetzung gibt, systematisiere ich die Ideen, die entstanden sind.

ps Einige Codes sind in der alten Notation geschrieben, ich hoffe auf Verständnis, der Zeit- oder Personalaufwand für das Refactoring wird nicht immer gefunden.

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


All Articles