Wczo­raj byłem zmu­szony szybko dodać dodat­kową funk­cjo­nal­ność do jed­nej z roz­wi­ja­nych przez nas apli­ka­cji. Widok, o który cho­dziło opiera się na auto­ma­tycz­nym, scaf­fol­do­wa­nym widoku admi­ni­stra­tora. Brzmi prosto?

Pro­blem pole­gał na tym, że dosto­so­wy­wa­nie logiki admi­ni­stra­tora jest dopiero w fazie pla­nów (gałąź admin-newforms). W związku z tym, roz­bu­do­wa­nie go o wła­sną logikę jest teo­re­tycz­nie na tym eta­pie niewykonalne.

Prze­ko­pa­łem się więc przez więk­szość kodu modułu django.contrib.admin i oka­zało się, że można Django nieco oszu­kać. Widoki są funk­cjami, z czego korzy­sta się czę­sto przy gene­ric views. Nie można po nich dzie­dzi­czyć, ale można wołać jeden z drugiego.

Brzydki hack

Do pliku urls.py na samym początku (przed sek­cją admin) dopi­su­jemy:

(r'^admin/aplikacja/model/add/', 'serwis.aplikacja.admin_views.add_stage'),

Następ­nie zakła­damy plik serwis/aplikacja/admin_views.py:

from django.contrib.admin.views import main

def add_stage(request, *args, **kwargs):
	post = request.POST.copy()
	if request.method == 'POST':
		post['user'] = request.user.id
	request.POST = post
	return main.add_stage(request, 'aplikacja', 'Model', *args, **kwargs)

Voila!

Parę słów wytłumaczenia

Pierw­sza część jest chyba oczy­wi­sta, w tym przy­padku nad­pi­su­jemy widok doda­wa­nia dla kon­kret­nego modelu. Ponie­waż Django prze­twa­rza mapę rutingu w takiej kolej­no­ści, w jakiej są podane regułki, nasza linijka prze­słoni znaj­du­jące się niżej auto­ma­tyczne reguły wbu­do­wa­nego panelu administratora.

Druga część to utwo­rze­nie osob­nego pliku dla wido­ków admi­ni­stra­cyj­nych (nie musi to być osobny plik, ale to dobra prak­tyka). W pliku tym umiesz­czamy defi­ni­cję wła­snego widoku add_stage.

W powyż­szym przy­kła­dzie doda­wany jest auto­ma­tycz­nie wła­ści­ciel dla two­rzo­nej instan­cji modelu. Nie bar­dzo można zro­bić to w samym modelu, bo model jest bez­sta­nowy i nie ma dostępu do stanu apli­ka­cji. Poza tym, chcemy, żeby pole to było usta­wiane tylko w tym przypadku.

Obiekt request.POST jest domyśl­nie immutable, więc two­rzymy jego kopię i wła­śnie tę kopię mody­fi­ku­jemy, a następ­nie pod­mie­niamy ory­gi­nalny obiekt.

Ostat­nia linijka powo­duje wywo­ła­nie ory­gi­nal­nego, magicz­nego widoku admi­ni­stra­tora, dostar­czo­nego przez Django. Z tą róż­nicą, że prze­ka­zu­jemy do niego nazwę apli­ka­cji i modelu. W przy­padku wywo­ła­nia bez­po­śred­niego, pocho­dzą one z wyra­że­nia regu­lar­nego w kon­fi­gu­ra­cji urls.py, tutaj musimy podać je jawnie.