Sie sind auf Seite 1von 7

5/6/2014 Digging up Django class-based views - 3 - The digital cat

http://lgiordani.github.io/blog/2014/02/14/digging-up-django-class-based-views-3/ 1/7
Thedigitalcat
Home
Aboutme
Archives
RSS
Search...
DiggingUpDjangoClassbasedViews3
Publishedon:Feb14th,2014
Tags:django,python
PostedbyLeonardoGiordani
ThispostreferstoDjango1.5.Pleasebewarnedthatsomeofthemattersdiscussedhere,somesolutions
orthegivencodecanbeoutdatedbymorerecentDjangoversions
Inthefirsttwoissuesofthisshortserieswewanderedaroundthebasicconceptsofclassbasedviewsin
Django,andstartedunderstandingandusingtwoofthebasicgenericviewsDjangomakesavailableto
you:ListViewandDetailView.Bothareviewsthatreadsomedatafromthedatabaseandshowthem
onarenderedtemplate.
ThisthirdissuewantstointroducethereadertotheclassbasedversionofDjangoforms.Thispostisnot
meanttobeafullintroductiontotheDjangoformlibraryrather,Iwanttoshowhowclassbasedgeneric
viewsimplementtheCUDpartoftheCRUDoperations(Create,Read,Update,Delete),theReadone
beingimplementedbystandardgenericviews.
Averybasicexample
TostartworkingwithCBFs(classbasedforms)letssayweareworkingwithaStickyNoteclasswhich
representsasimpletextnotewithadate:
1
2
3
class StickyNote(models.Model):
timestamp = models.DateTimeField()
text = models.TextField(blank=True, null=True)
Oneofthefirstthingsweusuallywanttodoistobuildaformthatallowstheusertocreateanewentry
inthedatabase,inthiscaseanewstickynote.Thefunctionalversionofsuchaformwouldbethe
following:
1
2
3
4
def note_add(request):
form = StickyNoteForm(request.POST or None)
if form.is_valid():
new_note = form.save()
5/6/2014 Digging up Django class-based views - 3 - The digital cat
http://lgiordani.github.io/blog/2014/02/14/digging-up-django-class-based-views-3/ 2/7
5
6
7
8
9
10
new_note.save()
return render_to_response('note_add.html')
urlpatterns = patterns('',
url(r'^note_add/$', 'note_add'),
)
whichisnottoocomplextograsp.NotethatIleftasidesomeimportstheStickNoteFormclassisbuilt
usingamodelform.Sinceyoualreadyknowhowfunctionalformviewswork,letscompareitwiththe
sameviewexpressedwithaclass:
1
2
class NoteAdd(CreateView):
model = StickyNote
Itisnosurprisethatmostofthecodewentaway,thankstoinheritance.Ashappenedinthefirsttwoposts
withstandardviews,theclassmechanismprovidesuswithabunchofcodethatlivessomewhereinthe
classhierarchyandworksbehindthescenes.Ourmissionisnowtouncoverthatcodetofigureouthow
exactlyCBFsworkandhowwecanchangethemtoperformwhatweneed.
Tomaketheposteasiertofollow,pleasealwaysrememberthatclassbasedformisashortnamefor
classbasedformview.Thatis,CBFsareviews,sotheirjobistoprocessincomingHTTPrequestsand
returnaHTTPresponse.Formviewsdothisinaslightlydifferentwaythanthestandardones,mostly
duetothedifferentnatureofPOSTrequestscomparedwithGETones.Letustakealookatthisconcept
beforemovingon.
HTTPrequests:GETandPOST
Pleasenotethatthisisabroadsubjectandthatthepresentsectionwantsonlytobeaveryquickreview
ofthemainconceptsthatarerelatedtoDjangoCBFs
HTTPrequestscomeindifferentforms,dependingonthemethodtheycarry.Thosemethodsarecalled
HTTPverbsandthetwomostusedonesareGETandPOST.TheGETmethodtellstheserverthatthe
clientwantstoretrievearesource(theoneconnectedwiththerelativeURL)andshallhavenoside
effects(suchaschangingtheresource).ThePOSTmethodisusedtosendsomedatatotheserver,the
givenURLbeingtheresourcethatshallhandlethedata.
Asyoucansee,thedefinitionofPOSTisverybroad:theserveracceptstheincomingdataandisallowed
toperformanytypeofactionwithit,suchascreatinganewentity,editingordeletingoneormoreof
them,andsoon.
KeepinmindthatformsarenotthesamethingasPOSTrequest.Asamatteroffact,theyareconnected
justincidentally:aformisawaytocollectdatafromauserbrowsingaHTMLpagewhilePOSTisthe
waythatdataistransmittedtotheserver.YoudonotneedtohaveaformtomakeaPOSTrequest,you
justneedsomedatatosend.HTMLformsarejustausefulwaytosendPOSTrequests,butnottheonly
one.
Formviews
Whyareformviewsdifferentfromstandardviews?Theanswercanbefoundlookingattheflowofa
typicaldatasubmissiononaWebsite:
1. Theuserbrowsesawebpage(GET)
2. TheserveranswerstheGETrequestwithapagecontainingaform
5/6/2014 Digging up Django class-based views - 3 - The digital cat
http://lgiordani.github.io/blog/2014/02/14/digging-up-django-class-based-views-3/ 3/7
3. Theuserfillstheformandsubmitsit(POST)
4. Theserverreceivesandprocessesdata
Asyoucanseetheprocedureinvolvesadoubleinteractionwiththeserver:thefirstrequestGETsthe
page,thesecondPOSTsthedata.SoyouneedtobuildaviewthatanswerstheGETrequestandaview
thatanswersthePOSTone.
SincemostofthetimetheURLweusetoPOSTdataisthesameURLweusedtoGETthepage,we
needtobuildaviewthatacceptsbothmethods.Thisisthemainreasonofthepatternyouareusedtosee
infunctionalformviewsinDjango.TheofficialDjangodocumentationonthesubjectusesthissnippetof
code
1
2
3
4
5
6
7
8
9
10
11
12
13
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render(request, 'contact.html', {
'form': form,
})
Asyoucanseethefirstconditionalpathdealswiththedatasubmission(POST)whiletheelsepartdeals
withtheusualcaseofaGETrequest.
NowitistimetodigintotheclassbasedformsthatDjangoprovidesustounderstandhowtheydealwith
thisdoubleinteraction.
LetusstartwiththeCreateViewclassweusedinoursimpleexample,whichisdefinedin
views/generic/edit.py#202.Itisanalmostemptyclassthatinheritsfrom
SingleObjectTemplateResponseMixinandfromBaseCreateView.Thefirstclassdealswiththe
templateselectedtorendertheresponseandwecanleaveitasideforthemoment.Thesecondclass,on
theotherhand,canbefoundacoupleoflinesabove,atviews/generic/edit.py#L187,andimplementstwo
methodswhichnamesareselfexplaining,get()andpost().
ProcessingGETandPOSTrequests
Wealreadymettheget()methodinthepastarticlewhenwetalkedaboutthedispatch()methodof
theViewclass.Aquickrecapofitspurpose:thismethodiscalledwhentheincomingHTTPrequest
carriestheGETverbandisusedtoprocesstherequestitself.Notsurprisingly,thepost()methodis
calledwhentheincomingrequestisaPOSTone.Thetwomethodsarealreadydefinedbyanancestorof
theBaseCreateViewclass,namelyProcessFormView(views/generic/edit.py#L145).Itisusefultotake
apeekatthesourcecodeofthislastclass:
1
2
3
4
5
6
7
8
class ProcessFormView(View):
"""
A mixin that renders a form on GET and processes it on POST.
"""
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
5/6/2014 Digging up Django class-based views - 3 - The digital cat
http://lgiordani.github.io/blog/2014/02/14/digging-up-django-class-based-views-3/ 4/7
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Asyoucanseethetwomethodsareprettystraightforward.Theybothretrievetheclassoftheformwith
get_form_class()andinstanceitwithget_form()(moreonthemlater).Theget()methodthenjust
callstherender_to_response()methodtorenderatemplate,passingthecontextproducedbythe
get_context_data()method.Notethatthecontextreceivestheformasbuiltbytheget_form()
method.
Thepost()methoddoesnotdirectlyrenderthetemplatesinceithastoprocessincomingdatabefore
doingthislaststep.Insteadthevalidationoftheformisperformedthroughitsis_valid()methodand
thetwomethodsform_valid()andform_invalid()arecalleddependingontheresultofthetest.See
theofficialdocumentationformoreinformationaboutformvalidation.
PleasenotethatthebehaviouroftheseclassesfollowsthesamepatternofthatusedinListViewand
DetailViewasdescribedintheprevioustwoposts.
TheProcessFormViewclassinheritsfromView,whichwasalreadydescribedindepthinthefirsttwo
postsofthisseriesthereyoucanfindtheas_view()anddispatch()methodthatarethefoundationof
theCBVssystem.
TheformworkflowpartI
TheinheritancepaththatstartswithProcessFormViewspansalltheclassesthatdealwiththeincoming
request,tellingGETandPOSTmethodsapart.Thesecondinheritancepathwecanfollowfrom
BaseCreateViewleadstoModelFormMixin,whichisdefinedatviews/generic/edit.py#L75.Thispath
containstheclassesthatimplementtheformmanagementmethods.Thefirsttwomethodsthatdealwith
theformareget_form_class()andget_form()thatweencounteredwhendiscussingtheget()and
post()methods.
Theget_form_class()triestogettheformmodelfromtheself.form_classattribute,andifthisis
notdefinedtriestoextractthemodelfromtheself.modelorfromthequeryset.Thenitreturnsasuitable
modelform,usingafactorydefinedinforms/models.py.
Theget_form()methodisdefinedinFormMixin(views/generic/edit.py#L10)andinstancestheform
classwiththekeywordsreturnedbyget_form_kwargs(),implementedinviews/generic/edit.py#L100.
Thislastmethodisquiteimportantaswearegoingtodiscoverinashortwhile,sinceithasabigrolein
thedoubleinteractionthathappenswithPOSTrequests.
Thefirstimplementationoftheget_form_kwargs()methodthatwefindintheancestorstreeisin
ModelFormMixin(views/generic/edit.py#L100),butthisimmediatelycallsthesamemethoddefinedin
FormMixin(views/generic/edit.py#L10).Thecodeis
5/6/2014 Digging up Django class-based views - 3 - The digital cat
http://lgiordani.github.io/blog/2014/02/14/digging-up-django-class-based-views-3/ 5/7
1
2
3
4
5
6
7
8
def get_form_kwargs(self):
kwargs = {'initial': self.get_initial()}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
Thefirstvalueoftheformkeywordsdictionaryisthecopyoftheself.initialdictionary,returnedby
get_initial()asstatedbytheofficialdocumentation.Then,ifthemethodoftherequestbeing
processedisPOSTorPUT,thekeywordsareupdatedwiththecontentoftherequestitself,i.e.posted
dataanduploadedfiles.Thisisusedtoinitializetheformobjectitselfasyoucanseeat
forms/forms.py#L77,andIamgoingtodescribethismechanismlater.
Afterthismethodreturnsitsdictionary,theexecutioncontinuesinModelFormMixin.Thecodeofthat
methodis
1
2
3
4
def get_form_kwargs(self):
kwargs = super(ModelFormMixin, self).get_form_kwargs()
kwargs.update({'instance': self.object})
return kwargs
thatjustaddsself.objectundertheinstancekeyofthekeywordsdictionary.Wealreadymet
self.objectwhendiscussingDetailView,whereitcontainedtheresultofthequeryset,i.e.theobject
beingshownbytheview.
Whatisself.objectnow?AmongtheancestorsofourCreateViewclass,BaseCreateViewdefines
self.objectasNonesoforthemomentwecanleaveitaside.Itwillcometotherescuelater,whenwe
willdiscussupdateanddeletionforms,sodonotforgetit.
Thelastthingswefindintheget()method,justafterget_form_class()andget_form(),is
get_context_data().AshappenedinListViewandDetailView,thismethodbuildsadictionary(the
context)thatisusedtorenderatemplate.Youcanfindtheimplementationofget_context_data()at
views/generic/edit.py#L130.Asyoucansee,sinceself.objecthasbeensettoNone,thecontext
containsonlytheinstancedformundertheformkeyword(insertedatviews/generic/edit.py#L155).
Letsrecaptheprocessuntilhere.
1. TheURLdispatcherrequestswithGETapagecontainingaform.
2. Theget()methodofProcessFormViewfindstheformclassofchoicethrough
get_form_class()
3. Theformclassisinstancedbyget_form()withthevaluescontainedintheself.initial
dictionary
4. Atthispointatemplateisrenderedwithacontextreturnedbyget_context_data()asusual.The
contextcontainstheform.
TheformworkflowpartII
Nowtheuserobtainedtherequestedpageandisfacinganemptyform.Oncetheformhasbeenfilled,he
orsheclicksthesubmitbuttonandanewHTTPrequestreachestheserver,thistimecarryingthePOST
methodandasetofdatatakenfromtheinputfieldsoftheformitself.Ourviewshallnowhandlethe
secondinteractionstep,theonethatinfunctionalviewsisusuallymanagedbythepartstartingwithif
request.method == 'POST':.
5/6/2014 Digging up Django class-based views - 3 - The digital cat
http://lgiordani.github.io/blog/2014/02/14/digging-up-django-class-based-views-3/ 6/7
Aswealreadyknow,theincomingrequestisprocessedbythepost()methodofProcessFormView,
whichworksliketheget()methodinitsfirstpart,callingget_form_class()andget_form().This
lattermethodnowdealswithaPOSTrequest,sothecodeofget_form_kwargs()inFormMixin
(views/generic/edit.py#L10)addstothekeywordsdictionarythesubmitteddatawiththedatakeyand
theuploadedfilesunderthefileskey.WhydoesDjangodothis?Well,asyoucanseeat
forms/forms.py#L77aDjangoformcanbeinstancedwithanoptionaldatakeyword,whichisstored
insidetheformobjectforthesubsequentvalidationphase.
Sonowtheformisbound(thatis,itcontainssomedataorfilesseeforms/forms.py#L80).Thepost()
methodnowteststheresultofis_valid()andactsaccordinglycallingeitherform_valid()or
form_invalid().Payattentionthat,whileis_valid()isamethodoftheformitself,thetwolatter
methodsbelongtotheBaseCreateView,definedbythesameancestorclassesthatimplement
get_form_kwargs(),ModelFormMixinandFormMixin.
Theformerclassimplementsitatviews/generic/edit.py#L123andputstheresultofform.save()into
self.object.Rememberthatself.objectisappendedtothecontextundertheobjectkeybythe
get_context_data()method,asshownintheprevioussection.Theform.save()methodfor
modelformsisdefinedbyBaseModelFormatforms/models.py#L357andbasicallysavestheinstanceof
theDjangomodelconnectedwiththemodelform,thatisimplementstheactualcreationatthebaseofthe
CreateViewformview.Asform.save()returnstheobjectsavedtothedatabase,itmakessensetostore
itinself.objectandpassittothetemplate.
Theexecutionofform_valid()continueswiththeimplementationintheFormMixinclassat
views/generic/edit.py#L61.ThismethodreturnsaHttpResponseRedirectobject,whichisthewayyou
makethebrowserpointtothegivenURLinDjango.InthiscasetheURLisgivenby
self.get_success_url()whichtriestoreturnself.success_urlifdefined,otherwisereturnsthe
resultofget_absolute_url()forthefreshmadeobject.
Ontheotherhand,form_invalid()atviews/generic/edit.py#L67dealswiththecaseofaform
containingsomeerrorsandsimplycallsrender_to_response()passingitthecontextwiththecompiled
formundertheformkey.
UpdateandDeleteoperations
ThisratherrichcodetourunveiledtheinnermechanismoftheCreateViewclass,whichcanbeusedto
createanewobjectinthedatabase.TheUpdateViewandDeleteViewclassesfollowasimilarpath,with
minorchangestoperformthedifferentactiontheyareimplementing.
UpdateViewwantstoshowtheformalreadyfilledwithvalues,soitinstancesself.objectbefore
processingtherequest(views/generic/edit.py#L210).Thismakestheobjectavailableinthekeywords
dictionaryundertheinstancekey(views/generic/edit.py#L105),whichisusedbymodelformsto
initializethedata(forms/models.py#L244).Theform.save()methodofBaseModelFormissmart
enoughtounderstandiftheobjecthasbeencreatedorjustchanged(forms/models.py#L365sothe
post()methodofUpdateViewworksjustliketheoneofCreateView.
DeleteViewisabitdifferentfromCreateViewandUpdateView.Astheofficialdocumentationstates,if
calledwithaGETmethoditshowsaconfirmationpagethatPOSTstothesameURL.So,asforthe
GETrequests,DeleteViewjustusestheget()methoddefinedbyitsancestorBaseDetailView
(views/generic/detail.py#L103),whichrendersthetemplateputtingtheobjectinthecontext.Whencalled
withaPOSTrequest,theviewusesthepost()methoddefinedbyDeletionMixin
(views/generic/edit.py#L233,whichinturnjustcallsthedelete()methodofthesameclass
(views/generic/edit.py#L239).Thisperformsthedeletiononthedatabaseandredirectstothesuccess
URL.
5/6/2014 Digging up Django class-based views - 3 - The digital cat
http://lgiordani.github.io/blog/2014/02/14/digging-up-django-class-based-views-3/ 7/7
Tweet 4 3
Conclusion
Asyoucansee,thestructurebehindthecurrentimplementationofDjangoclassbasedformviewsis
rathercomplex.ThisallowstheusertoachievecomplexbehavioursliketheCUDoperationsjustby
definingacoupleofclassesasIdidinthesimpleexampleatthebeginningofthepost.Mostofthetime,
however,suchasimplificationmakesitdifficultfortheprogrammertounderstandhowtoachievethe
desiredchangestotheclassbehaviour.SothepurposeofthisbigtourImadeinsidetheDjangosource
codewastogiveaninsightofwhatmethodsarecalledinthelifetimeofyourHTTPrequestsothatyou
canbetteridentifywhatmethodsyouneedtooverride.
WhenperformingspecialactionsthatfalloutsidethestandardCUDoperationsyoubetterinheritfrom
FormView(views/generic/edit.py#L181).Thefirstthingtodoistocheckifandhowyouneedto
customizetheget()andpost()methodsrememberthatyoueitherneedtoimplementthefullbehaviour
ofthosemethodsormakeyouchangesandcalltheparentimplementation.Ifthisisnotenoughforyour
applicationconsideroverridingoneofthemorededicatedmethods,suchasget_form_kwargs()or
form_valid().
ThispostendstheseriesDiggingUpDjangoClassbasedViews.Staytunedforotherupcoming
articlesonDjango!
FeelfreetousetheblogGoogle+pagetocommentthepostortoaskforanindepthanalysisofsome
topic.TheGitHubissuespageisthebestplacetosubmitcorrections.
Previousarticles
DiggingUpDjangoClassbasedViews1
DiggingUpDjangoClassbasedViews2
Copyright2014LeonardoGiordani.PoweredbyOctopress|ThemefabricbyPankajKumar
ToTop

Das könnte Ihnen auch gefallen