Bitterkalt
Überall in Deutschland frieren die Menschen. Überall? Nein, ein kleines Dorf am Rand der Scheibe wehrt sich gegen die Kälte.
Hier regnet es sogar. ![]()
Spass mit Newforms-Admin - Ganz schnell Widgets zuweisen
In den vergangenen Spass mit Newforms-Admin Beiträgen hab ich immer wieder gezeigt, wie man das Widget (die HTML-Ausgabe) eines Feldes beeinflusst und ändert.
Als Beispiel nochmal ein simples Model, bei dem letztendlich der Titel in großer Schrift ausgegeben werden soll.
1 2 3 4 5 | from django.db import models class Foobar(models.Model): title = models.CharField(max_length=100) text = models.TextField(blank=True) |
Also basteln wir uns – wie gehabt – eine ModelAdmin-Klasse und darin eine Methode formfield_for_dbfield. Diese durchläuft alle Felder und wenn sie auf den passenden Titel trifft, ändert sie das Widget dazu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from django.contrib import admin from django import forms from django.utils.safestring import mark_safe from .models import Foobar class BigTitleWidget(forms.widgets.Input): def render(self, name, value, attrs=None): field = '<input name="%s" value="%s" style="font-size:1.5em"/>' % (name, value) return mark_safe(field) class FoobarAdmin(admin.ModelAdmin): def formfield_for_dbfield(self, db_field, **kwargs): field = super(FoobarAdmin, self).formfield_for_dbfield(db_field, **kwargs) if db_field.name == "title": field.widget = BigTitleWidget() return field admin.site.register(Foobar, FoobarAdmin) |
Aber ist das nicht super hässlich? Irgendwann bin ich mal über einen Codeblock von Jannis gestolpert und hab gesehen, dass die ModelAdmin-Klasse ein Attribut form besitzt, dass das automatisch generierte ModelForm mit einem eigenen überschreiben kann. Hey, und das ist sogar dokumentiert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from django.contrib import admin from django import forms from django.utils.safestring import mark_safe from .models import Foobar class BigTitleWidget(forms.widgets.Input): def render(self, name, value, attrs=None): field = '<input name="%s" value="%s" style="font-size:1.5em"/>' % (name, value) return mark_safe(field) class FoobarForm(forms.ModelForm): title = forms.CharField(widget=BigTitleWidget()) class FoobarAdmin(admin.ModelAdmin): form = FoobarForm admin.site.register(Foobar, FoobarAdmin) |
Das ist doch viel schöner! ![]()
Also nie wieder formfield_for_dbfield? Nicht ganz, denn innerhalb des ModelForms hat man keinen Zugriff auf die anderen Parameter des aktuellen Admin-Views. Will man dem Widget weitere Parameter übergeben (wie z.B. die aktuelle User-ID, hier erklärt, führt der Weg weiterhin über formfield_for_dbfield.
Unter der Lupe: Template-Context-Prozessoren
Djangos Templates stehen zwei unterschiedliche Arten zur Verfügung, wie Variablen ins Template gelangen (Template-Filter und -Tags jetzt außen vorgelassen). Die lokalen Variablen, die pro View festgelegt werden und die Template-Context-Prozessoren (im folgenden TCP genannt), jeweils ein Dictionary mit Variablen, das allen Templates zur Verfügung steht.
Einige TCPs sind in der Grundkonfiguration eines Django-Projekts schon mit dabei und bestimmt hast du sie schon einmal gesehen:
1 2 3 4 5 | TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.auth', 'django.core.context_processors.i18n', 'django.core.context_processors.media', ) |
Hinweis: Wenn du ein neues Projekt mittels django-admin.py startproject foo erstellt hast, ist dieser Eintrag nicht in der settings.py vorhanden. Er steht aber in den Grundeinstellungen django.conf.global_settings. Wenn du etwas ändern willst, packe den oberen Schnipsel ans Ende deiner settings.py.
Diese Tupel wird in der Einstellung settings.TEMPLATE_CONTEXT_PROCESSORS festgelegt. Am bekanntesten wird wohl der media-TCP sein, er stellt allen Templates die Variable MEDIA_URL zur Verfügung, ein Link zum Ordner mit den statischen Mediendateien.
TCPs können natürlich auch selbst erstellt werden, die Syntax ist sehr einfach:
1 2 3 4 | def mein_prozessor(request): return { 'VARIABLEN_NAME': 'wert', } |
Es ist eine einfache Funktion die als einziges Argument die aktuelle request-Variable nimmt und ein Dictionary zurückgibt. Eingebunden wird sie in die settings.py wie oben schon beschrieben:
1 2 3 4 5 6 | TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.auth', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'mein_app.context_processors.mein_prozessor', ) |
Es ist eine gute Idee, die Datei die diese Funktion beinhaltet, auch 'context_processors.py' zu nennen.
Nochmal im Vergleich
Da sich mit Quellcode eine Sache am besten verstehen lässt, hier noch einmal ein einfacher View:
1 2 3 4 5 6 7 | def startseite(request): return render_to_response( 'pfad/zum/template.html', {'headline': 'Meine Startseite', 'text': 'Lorem ipsum dolor bla.'}, RequestContext(request) ) |
Die hier benutzte render_to_response Funktion gibt das Template (erstes Argument) zurück mit den lokalen Variablen (zweites Argument) und stellt auch alle globalen TCPs bereit (drittes Argument). In einer flauschigen Grafik schaut das so aus:
Was nutzt man wann?
Wann man TCPs nutzt dürfte nun klar sein. Soll ein Wert jedem Template zur Verfügung stehen, baut man dafür einen TCP. Ein Beispiel ist auf dieser Seite meine Fußleiste, dort sind auf jeder Seite die neuesten Blogbeiträge und Bookmarks zu sehen.
Der FAQ-Teil
Nun bist du vielleicht auf diese Seite gelangt, weil man dich im Django-Channel hierher verwiesen hat. Jetzt kommen die häufigsten Fehler im Umgang mit Template-Context-Prozessoren (TCP).
Eine typisches Problem dürfte zum Beispiel sein:
Ich kann in meinem Template die
MEDIA_URLVariable nicht nutzen, sie gibt nur einen leeren String zurück.
Mögliche Fehlerquellen
1. Groß/Kleinschreibung
Ganz simpel eigentlich. In Django-Templates werden Variablen zwischen Groß- und Kleinschreibung unterschieden. Media_URL ist also nicht das selbe wie MEDIA_URL.
2. Du hast einen eigenen View geschrieben der render_to_response nutzt
Der häufigste Fehler ist, dass du render_to_response vergessen hast, als drittes Argument den Request-Context zur Verfügung zu stellen. Schau nochmal auf die Beispielfunktion von oben:
1 2 3 4 5 6 7 8 | from django.template.context import RequestContext def startseite(request): return render_to_response( 'pfad/zum/template.html', {'headline': 'Meine Startseite', 'text': 'Lorem ipsum dolor bla.'}, RequestContext(request) ) |
RequestContext analysiert im Prinzip die Module aus settings.TEMPLATE_CONTEXT_PROCESSORS und stellt sie dem Template zur Verfügung.
Djangos Generic-Views machen das automatisch! Warum render_to_repsonse nicht? Im Prinzip geht es darum, dass du die Möglichkeit hast, diese Variablen nicht dem Template zur Verfügung zu stellen. Klingt komisch, ist aber so. Oft ist dies der Fall, wenn Dritte Zugriff auf die Templates haben und nicht alle globalen Variablen wissen sollen.
Das ist aber nicht DRY *kreisch*. Es gibt Mittel und Wege das semi-automatisch zu machen. Auf dieser Seite sind einige Shortcuts erklärt.
3. Du willst auf den request TCP zugreifen
Bis vor Django 1.0 war der TCP django.core.context_processors.request noch Teil der Grundeinstellungen. Dieser wurde nun (aus Sicherheits- oder Performancegründen, ich weiß es nicht) entfernt. Viele 3rd-Party-Apps nutzen diesen TCP ohne zu dokumentieren, wie er eingebunden wird.
Wenn du in deiner settings.py noch keine Definition für TEMPLATE_CONTEXT_PROCESSORS hast, packe diesen Schnipsel ans Ender der Datei:
1 2 3 4 5 6 | TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.auth', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.core.context_processors.request', ) |
Alternativ, wenn du schon eine Prozessoren-Liste hast, packe einfach django.core.context_processors.request dazu.
Das wars, eine erschöpfende Antwort auf die #1 FAQ im Django IRC-Channel. ![]()
Request-unabhängige Locale-Einstellungen
Manchmal ist es nötig, in einem Request/View verschiedene Locale-Einstellungen zu nutzen. Der häufigste Fall dürfte der Versand von E-Mails an verschiedene User sein, die jeweils die Nachricht in ihrer Sprache erhalten sollen.
Man könnte einfacherweise für diese E-Mail für jede Sprache ein eigenes Template anlegen, aber wozu gibt es die gettext-Unterstütung.
Djangos I18N-Kern bringt eine Funktion django.utils.translation.activate mit, mit der man zur Laufzeit die Locale-Einstellung ändern kann.
Als Beispiel dient ein einfaches Template mit einem Translation-String.
1 2 3 4 | {% spaceless %} {% load i18n %} {% trans "Thanks for using our site!" %} {% endspaceless %} |
Und eine Dummy-Funktion. Dabei wird über die Sprachen iteriert (normalerweise würde die jeweilige Spracheinstellung im Userprofil gespeichert sein) und das email.txt Template mit der jeweiligen Sprache im Terminal ausgegeben:
1 2 3 4 5 6 7 8 9 10 11 | from django.template.loader import render_to_string from django.utils.translation import get_language, activate current_language = get_language() languages_to_render = ('en-us', 'de-de', 'pt-br', 'fr') for lang in languages_to_render: activate(lang) print render_to_string('email.txt') activate(current_language) |
Ausgabe:
1 2 3 4 | Thanks for using our site! Vielen Dank, dass Sie unsere Seite benutzen! Obrigado por usar nosso site! Merci d'utiliser notre site ! |
Einfach oder? Beachten sollte man noch, dass man den originalen Locale-Wert vor dem ändern speichert (django.utils.translation.get_language liefert den aktuellen Wert) und am Ende wieder aktiviert. Sonst würde in diesem Fall der Rest des Views mit der französischen Locale weiterlaufen.
Hackfleischpizza
Wohoo, endlich wieder Foodcontent. ![]()
Heute: Wir basteln uns eine Hackfleischpizza. Ok, dass ist nun nicht soo was besonderes aber sei's drum, es ist lecker und schnell gemacht. Wir brauchen:
- einen Pizzateig
- 300g Rinderhack
- eine Mozarellakugel (nennt man das so?)
- eine Hand voll Mais
- eine Zwiebel
Das Hack wird in der Pfanne erst einmal angebraten, aber nicht ganz durch. Danach von der Pfanne erst einmal auf ein Küchentuch legen damit das überflüssige Öl abläuft, sonst schwimmt die Pizza später. Der Mozarella wird grob gewürfelt und die Zwiebel in Ringe geschnitten. Alles zusammen mit einer Handvoll Mais (hier aus der Dose) auf der Pizza verteilen und für ca. 20 Minuten bei 180°C in den Ofen.
Lääääcka! Dazu gibt es einen weichen 1975'er Spätburgunder am Besten ein Bier nach Wahl. ![]()
Noch ein paar Gedanken dazu:
Wer kann, macht sich den Pizzateig natürlich selber. Allerdings sagt mir mein Hobbykochherz, dass ein Hefeteig nicht so einfach herzustellen ist und schnell geht es schon gar nicht (der sollte über Nacht im Kühlschrank noch gehen).
Ich für meinen Fall kaufe immer die fertigen Pizzateige inkl. Tomatensauce. Dabei sollte man nicht auf den Cent gucken. Die billigsten Fertigteige der Discounter schmecken mir nicht, sind dröge und die Sauce ist völlig überwürzt.
Der Fertigteig von Knack&Back für knapp 2€ war jedenfalls lecker. Ich hab aber nur nach ihm gegriffen, weil mir das Männchen auf der Packung gefallen hat. Ja, ich bin der Traum des Einzelhandels. ![]()
Und dann noch zum Belag. Leider ist es wohl eine deutsche Eigenart, eine Pizza mit hundert verschiedenen Belägen voll zu knallen, darüber noch eine 3cm dicke Käseschicht. Dabei ist weniger viel mehr, die Pizza ist viel leckerer je weniger Zutaten dabei sind.
Eine bessere settings.py
Bei einem frischen Django-Projekt ist so ziemlich die erste Änderung bei mir die settings.py.
Es gibt einige Sachen, die fehlen in der Grundkonfiguration immer. So z.B. der request-Context-Prozessor, Caching-Einstellungen oder die Mailserver-Einstellungen (ich betreibe lokal keinen SMTP). Daneben natürlich noch die Sachen, die ich in Wiederverwendbare Django-Projekte gepostet habe.
Irgendwann habe ich mir mal eine generische settings.py erstellt in der ich auf Anhieb alle, für mich nötigen, Einstellungen vorfinde und diese mit sinnvollen Werten vorgeingestellt. Bei jedem neuen Projekt überschreibe ich die settings.py zuerst einmal mit dieser Variante. Vielleicht findet es ja jemand interessant. ![]()
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | import os PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) PROJECT_NAME = os.path.split(PROJECT_ROOT)[-1] # ============================================================================== # debug settings # ============================================================================== DEBUG = True TEMPLATE_DEBUG = DEBUG INTERNAL_IPS = () if DEBUG: TEMPLATE_STRING_IF_INVALID = 'STRING_NOT_SET' # ============================================================================== # cache settings # ============================================================================== CACHE_BACKEND = 'locmem://' CACHE_MIDDLEWARE_KEY_PREFIX = '%s_' % PROJECT_NAME CACHE_MIDDLEWARE_SECONDS = 600 # ============================================================================== # email and error-notify settings # ============================================================================== ADMINS = ( # ('Your Name', 'your_email@example.com'), ) MANAGERS = ADMINS DEFAULT_FROM_EMAIL = 'from-mail@example.com' SERVER_EMAIL = 'error-notify@example.com' EMAIL_SUBJECT_PREFIX = '[%s] ' % PROJECT_NAME EMAIL_HOST = 'localhost' EMAIL_PORT = 25 EMAIL_HOST_USER = '' EMAIL_HOST_PASSWORD = '' EMAIL_USE_TLS = False # ============================================================================== # auth settings # ============================================================================== LOGIN_URL = '/accounts/login/' LOGOUT_URL = '/accounts/logout/' LOGIN_REDIRECT_URL = '/' # ============================================================================== # database settings # ============================================================================== DATABASE_ENGINE = 'sqlite3' DATABASE_NAME = os.path.join(PROJECT_ROOT, 'dev.db') DATABASE_USER = '' DATABASE_PASSWORD = '' DATABASE_HOST = '' DATABASE_PORT = '' # ============================================================================== # i18n and url settings # ============================================================================== TIME_ZONE = 'Europe/Berlin' LANGUAGE_CODE = 'de' LANGUAGES = (('en', 'English'), ('de', 'German')) USE_I18N = True SITE_ID = 1 MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'site_media') MEDIA_URL = '/media/' ADMIN_MEDIA_PREFIX = '/django_admin_media/' ROOT_URLCONF = '%s.urls' % PROJECT_NAME # ============================================================================== # application and middleware settings # ============================================================================== INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'django.contrib.humanize', ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.http.ConditionalGetMiddleware', # 'django.middleware.gzip.GZipMiddleware', 'django.middleware.common.CommonMiddleware', ) TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.auth', 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.core.context_processors.request', ) TEMPLATE_DIRS = ( os.path.join(PROJECT_ROOT, 'templates'), ) TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.load_template_source', 'django.template.loaders.app_directories.load_template_source', # 'django.template.loaders.eggs.load_template_source', ) # ============================================================================== # the secret key # ============================================================================== try: SECRET_KEY except NameError: SECRET_FILE = os.path.join(PROJECT_ROOT, 'secret.txt') try: SECRET_KEY = open(SECRET_FILE).read().strip() except IOError: try: from random import choice SECRET_KEY = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) secret = file(SECRET_FILE, 'w') secret.write(SECRET_KEY) secret.close() except IOError: Exception('Please create a %s file with random characters to generate your secret key!' % SECRET_FILE) # ============================================================================== # third party # ============================================================================== # ..third party app settings here # ============================================================================== # host specific settings # ============================================================================== try: from local_settings import * except ImportError: pass |
Git-Branch im Bash-Prompt anzeigen
Wer mit Git arbeitet und öfter mal Dateien im falschen Branch ändert – wie ich
– dem hilft vielleicht, den aktuellen Branch im Command-Prompt anzuzeigen. Das ganze schaut bei mir so aus:
Der passende Eintrag für deine .profile oder .bashrc Datei lautet:
1 | export PS1='\[\033[01;32m\]\u\[\033[01;34m\] \w\[\033[31m\]$(__git_ps1 " (%s)")\[\033[01;34m\]$\[\033[00m\] ' |
Den Zauber macht die Kette $(__git_ps1 " (%s)") möglich. __git_ps1 ist ein Befehl aus dem contrib/completion Verzeichnis der Git-Source-Distribution der den aktuellen Branch des Verzeichnisses ausgibt. Der wird aber hier unter Ubuntu mit dem Paket direkt mitinstalliert.
Wer OSX nutzt und Git via Macports installiert hat, muss wahrscheinlich die Datei /opt/local/etc/bashcompletion.d/git sourcen. Danke Horst für die Info!
Und wer noch mehr wissen will, findet bei asemanfar.com weitere Promt-Varianten.
Durchwahl
Ich habe eine Telefonnummer, eine Durchwahlnummer für die Arbeit bei mir zu Hause. Also nicht die Bürodurchwahl sondern noch geheimer, die Nummer landet ja privat hier. Diese Nummer ist nur meiner Firma und einer Hand voll Kunden bekannt. Und diese Nummer nutze ich so selten, dass ich sie nicht mal aus dem Kopf weiß sondern immer in der FritzBox nachgucken muss. Im Telefonbuch oder so steht sie natürlich auch nicht.
Eben diese Nummer wird nun schon seit 2 Wochen permanent von Outbound-Callcentern belagert. Wo haben die diese Nummer her? Ich fürchte es hat etwas damit oder einem noch unbekannten Fall zu tun... ![]()
Der Mythos vom statischen Servieren
In Djangos Dokumentation wird empfohlen, statische Dateien durch den Webserver ausliefern zu lassen. Was bedeutet das? Django läuft doch auf dem Webserver.

Im Prinzip geht es darum, Dateien die Django nicht bearbeiten muss, nicht durch den Django-Prozess zu schleifen. Statische Dateien sollen also den direkten Weg von der Festplatte zum Webserver finden (grüne Linie).
Das – ich nenne es mal – Problem ist, dass Django eine Funktion bereit stellt, statische Dateien durch Django zu schleifen: How to serve static files.

Hierbei wandern alle statischen Dateien durch den Django-Prozess anstatt dass sie direkt an den Webserver geliefert werden. Das ist ganz klar ineffizient.
Warum gibt's das dann? In erster Linie ist es für Entwickler die den Devserver nutzen gedacht und während der Entwicklung keinen zusätzlichen Webserver zum Static-Serving aufsetzen möchten.
Fazit also: Vermeide Static-Serving durch Django um jeden Preis – es gibt natürlich Ausnahmen, siehe weiter unten.
PHP macht das doch auch so
Letzens hatte mich ein PHP-Entwickerl auch gefragt, wieso er da nicht Django nehmen kann, dass geht doch auch mit PHP.
Oberes ist ein Zitat aus dem #django-de Channel. PHP macht es aber (unbewusst) genauso wie Django. Statische Dateien werden auch direkt vom Webserver geliefert und nicht erst durch den PHP-Prozessor geschleift. Dieser verarbeitet im Normalfall nur Dateien mit der Endung .php.
Ein Static-Serving durch PHP würde etwa so aussehen, dass der Webserver ein Stylesheet mit 'GET /stylesheet.php' anfordert, und diese Datei das unten gezeigte Script ausführt.
1 | <?php echo file_get_contents('/var/www/static/stylesheet.css'); ?> |
Ziemlich dämlich, oder? Aber leider alles schon gesehen. ![]()
Spezialisten
Überlasse das Static-Serving einem Server der besser dafür geeignet ist, zum Beispiel nginx oder Lighty.
Den oberen Satz hört man sehr häufig wenn es es Static-Serving geht. Was ist gemeint?
Zuerst einmal, nginx und lighttpd, oder auch liebevoll lighty genannt, sind Webserver, die auf das Ausliefern statischer Dateien optimiert sind. Dabei sind sie nicht unbedingt schneller als der Apache, ihre Stärke liegt in der Auslieferung von mehreren tausend Dateien pro Sekunde, denn dabei sind sie weniger RAM- und CPU-hungrig und skalieren besser.
Es gibt einen Weg, das beste beiden Webservern (Apache für Django, nginx für statische Dateien) herauszuholen. Dabei übernimmt nginx (oder lighty) als Frontend-Server alle Anfragen des Webbrowsers und serviert die statischen Dateien direkt. Anfragen auf dynamische Dateien werden an den Apache-Prozess weitergeleitet, der auf einem anderen Port (also nicht :80) lauscht. (Konfigurationsbeispiel)

Das Ganze ist dann schon ein recht kompliziertes Setup. Meine persönliche Meinung ist, wenn man den Punkt erreicht hat, dass ein spezialisierter Webserver für statische Dateien nötig ist, sollte man den statischen Kram auf eine eigene Maschine auslagern:

Letztendlich ist alles eine Frage des Budgets. Nicht unerwähnt soll bleiben, dass es auch Firmen gibt, die sich auf das Static-Serving spezialisiert haben. Akamai ist ein Vertreter aber auch Amazons S3 Service ist hervorragend dafür geeignet.
Was wäre wenn?
Es gibt natürlich auch das Problem, dass man statische Dateien durch Django schleifen muss. Beispiele:
-
Die Datei soll nur für bestimmte, authentifizierte User verfügbar gemacht werden.
Django stellt für solche Fälle eine Möglichkeit zur Verfügung, dass sich der Apache an Djangos Auth-Backend koppeln kann. Mehr Infos dazu in der Dokumentation. -
Die Datei wird dynamisch erzeugt, zum Beispiel ein PDF-Export der aktuellen Seite.
Vielleicht wäre hier eine Lösung, die Datei im Medienordner zu speichern und einen Location-Header zu dieser Datei zu senden. Dann übernimmt der Webserver wieder das Ausliefern der statischen Dateien und man erhält zudem ein statisches Caching der Dateien.
Jedenfalls, wenn es denn nicht anders möglich ist versuche große Dateien als Iterator an das Response-Objekt zu übergeben. Damit wird nicht die gesamte Datei in den Prozess geladen (bei einer 80MB Datei wäre der wsgi-Prozess dann 90MB+ groß) sondern in kleinen Häppchen (Chunks) durchgeschleift.
Beachte dazu aber, dass die GZip-Middleware diesen Prozess zerstören kann: Ticket
#7581. Aber auch dafür gibt es eine
einfache, performante Lösung. ![]()
Noch ein wichtiger Nachtrag:
Auch wenn in den Bildern mod_python angegeben ist, empfehle ich euch doch
ganz stark mod_wsgi als Kleber zwischen
Django und Apache. Es ist resoucen schonender, performanter und allgemein sympathischer. Leider aber hat mod_wsgi kein Logo das ich benutzen konnte. ![]()
Mehr dazu
Mehr Lesestoff und vor allem Konfigurations-Beispiele für verschiedene Webserver-Szenarien findest du im deutschsprachigen django-hosting Wiki.