Django er det første rammeverket der et tutorial-prosjekt ble til noe jeg fortsatte å bygge på etterpå i stedet for å slette mappen.
Jeg hadde gjort den klassiske "bygg en blogg med Django"-tutorialen som alle gjør, lukket mappen, og gikk videre. Det som faktisk satte seg kom senere: et CRM jeg bygde for å administrere noen fiktive kundedata (kunder, notater, oppfølginger), en live chat-app, og en restaurantmeny-side. Tre separate prosjekter, tre forskjellige grunner til at Djangos batteries-included-tilnærming sluttet å føles overdrevet og begynte å føles som grunnen til at jeg ikke måtte bygge mitt eget auth-system for fjerde gang.
CRM-listesiden som brukte fire sekunder uten grunn
CRM-ets kundelisteside viser hver kunde sammen med hvor mange notater som ligger på filen deres. Med rundt to hundre fiktive kunder i testdatabasen brukte den siden over fire sekunder på å laste, noe som føltes absurd for noe som burde vært én databasespørring unna å være øyeblikkelig.
# views.py, the version that was slow
def customer_list(request):
customers = Customer.objects.all()
return render(request, "crm/customer_list.html", {"customers": customers}){% for customer in customers %}
<tr>
<td>{{ customer.name }}</td>
<td>{{ customer.notes.count }}</td>
</tr>
{% endfor %}Ser helt fornuftig ut. customer.notes.count i malen, én gang per kunde. Ingenting her skriker "dette er buggen."
hva jeg faktisk fant da jeg gikk og lette
Jeg hadde hørt om Django Debug Toolbar men aldri giddet å installere den før denne siden faktisk gjorde meg nysgjerrig. Den viste noe sånt som dette nederst på siden:
SQL queries: 201 (198 similar)To hundre og én spørringer, for å rendre to hundre kunder. Én spørring for å hente kundelisten, og så én til spørring per kunde, utløst av den uskyldig utseende customer.notes.count i malen. Hver iterasjon av løkken traff databasen igjen, separat, fordi Djangos ORM er lat: customer.notes hentes ikke før noe faktisk spør etter det, og malen som spør etter det inne i en løkke betyr å spørre to hundre separate ganger.
hva jeg googlet, og begrepet jeg ikke visste jeg trengte
"django template loop slow multiple database queries" tok meg til det faktiske navnet på dette nesten umiddelbart: N+1-spørringsproblemet. Én spørring for å hente N rader, så N til spørringer for å hente relaterte data for hver rad, én om gangen, i stedet for én spørring som henter alt på forhånd. Så snart jeg hadde navnet, var fiksen én linje.
# views.py, the fixed version
from django.db.models import Count
def customer_list(request):
customers = Customer.objects.annotate(note_count=Count("notes"))
return render(request, "crm/customer_list.html", {"customers": customers}){% for customer in customers %}
<tr>
<td>{{ customer.name }}</td>
<td>{{ customer.note_count }}</td>
</tr>
{% endfor %}annotate med Count gjør tellingen inne i den ene databasespørringen, som en GROUP BY, i stedet for én gang per rad i Python etterpå. Samme to hundre kunder, én spørring totalt, sidelastetiden falt fra fire sekunder til noe jeg faktisk ikke kunne oppfatte tok noen tid i det hele tatt.
ORM-en sluttet å være en svart boks rundt her
Når du kommer fra å skrive rå SQL i mindre script, føltes Djangos ORM i starten som den gjorde noe magisk og litt mistenkelig, helt til det øyeblikket jeg faktisk trengte å legge til et felt til CRM-ets kundemodell etter at det allerede var ekte data i databasen, og migrasjoner gjorde tingen de skal gjøre uten at jeg måtte skrive en ALTER TABLE for hånd.
python manage.py makemigrationsMigrations for 'crm':
crm/migrations/0004_add_customer_notes.pypython manage.py migrate
Det er øyeblikket Django sluttet å være "en haug med filer jeg ikke skjønner ennå" og begynte å være et verktøy jeg stolte på ikke stille å ødelegge dataene mine, i tillegg til å være tingen som nettopp stille hadde kostet meg to hundre ekstra spørringer en sidelasting tidligere.
live chat-appen ødela mentalmodellen min en hel kveld
Djangos normale request-response-syklus antar at en forespørsel kommer inn, du gjør noe, du sender et svar, ferdig. En live chat trenger en tilkobling som holder seg åpen, noe som betydde å lære Channels og asynkrone consumers. Første forsøk kjørte jeg den bare med den vanlige dev-serveren:
python manage.py runserverSom gladelig starter, gladelig serverer normale sider, og så gjør absolutt ingenting nyttig i det øyeblikket en klient prøver å åpne en WebSocket-tilkobling. Ingen feilmelding, ingen krasj, bare en tilkobling som aldri oppgraderes. Djangos standard WSGI-dev-server vet ikke hva den skal gjøre med en protokolloppgraderingsforespørsel i det hele tatt, den er bygget for request-response-modellen og ingenting annet.
# consumers.py
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.channel_layer.group_add("chat", self.channel_name)
await self.accept()
async def receive(self, text_data):
await self.channel_layer.group_send(
"chat", {"type": "chat.message", "message": text_data}
)
async def chat_message(self, event):
await self.send(text_data=event["message"])Consumer-koden selv var fin, mer eller mindre på det første ekte forsøket. Den faktiske fiksen var å innse at jeg måtte kjøre den gjennom Daphne, en ASGI-server, i stedet for den vanlige WSGI-dev-serveren, siden ASGI er protokollen som faktisk forstår langvarige tilkoblinger i utgangspunktet.
daphne -p 8001 myproject.asgi:applicationSå snart jeg hadde den riktige serveren som faktisk snakket riktig protokoll, fungerte consumeren som hadde sett ødelagt ut en hel kveld på den første ekte testen. Jeg hadde ikke ødelagt noe grunnleggende med rammeverket. Jeg hadde bare kjørt den gjennom den ene serveren som aldri kom til å fungere for dette.
restaurantmeny-prosjektet lærte meg at admin-panelet er undervurdert
Ingenting komplisert her, bare menyelementer, kategorier, priser, en offentlig side. Den interessante delen var å innse at det innebygde admin-panelet betydde at jeg aldri måtte bygge et CMS for at en klient skulle kunne oppdatere priser selv.
# admin.py
from django.contrib import admin
from .models import MenuItem, Category
admin.site.register(Category)
admin.site.register(MenuItem)To linjer, og plutselig finnes det et fungerende, tillatelsesstyrt admin-grensesnitt for å redigere hvert eneste menyelement, ikke noe separat CMS-bygg nødvendig. Føltes som juks første gang, på den gode måten.
demo-dataene gikk nesten ut til en ekte person
Liten, litt pinlig sidebemerkning: de fiktive CRM-dataene jeg brukte til testing inkluderte en kunde ved navn "Test Testerson" med et notatfelt som bare sa "dette er fiktivt, ignorer." Jeg nesten sendte en skjermopptak av CRM-et til noen som en demo uten å sjekke hvilken kundejournal som var på skjermen først. Fanget det opp før sending, men det er nå en permanent vane faktisk å skanne de synlige radene før jeg spiller inn noe, ikke bare stole på at testdata forblir begrenset til min egen skjerm.
hva jeg ville fortalt noen som starter med Django etter Flask
Flask, som er der de tidligere CLI- og GUI-gjøremålsappene mine mest bodde, får deg til å bygge alt, noe som lærer deg mye. Django antar at du vil ha batteriene inkludert, raskere for et ekte prosjekt og tregere å faktisk forstå i starten, siden det er mer rammeverk som står mellom deg og forespørselen. Begge har rett av forskjellige grunner. Jeg trengte bare alle tre, CRM-et, chat-appen, og menysiden, pluss én genuint dårlig ettermiddag med uforklarlig treghet, før Djangos versjon av "raskere" begynte å føles fortjent i stedet for som magi jeg ikke hadde betalt for ennå.
hva jeg ville gjort annerledes
Installere Django Debug Toolbar dag én på ethvert nytt Django-prosjekt, ikke etter at en side allerede har gjort meg mistenksom nok til å gå og lete etter den. N+1-problemet satt der fra selve den aller første versjonen av den viewen. Jeg hadde bare ingen måte å se det på før jeg gikk og skaffet meg en.