django girls tutorial extensions
DESCRIPTION
Django Girls TutorialTRANSCRIPT
1. Introduction2. Homework:addmoretoyourwebsite!3. Homework:secureyourwebsite4. Homework:createcommentmodel5. Optional:PostgreSQLinstallation6. Optional:Domain7. DeployyourwebsiteonHeroku
TableofContents
DjangoGirlsTutorial:Extensions
2
InfoThisworkislicensedundertheCreativeCommonsAttribution-ShareAlike4.0InternationalLicense.Toviewacopyofthislicense,visithttp://creativecommons.org/licenses/by-sa/4.0/
Thisbookcontainsadditionaltutorialsyoucandoafteryou'refinishedwithDjangoGirlsTutorial.
Currenttutorialsare:
Homework:addmoretoyourwebsite!Homework:secureyourwebsiteHomework:createcommentmodelOptional:PostgreSQLinstallation
ThistutorialsaremaintainedbyDjangoGirls.Ifyoufindanymistakesorwanttoupdatethetutorialpleasefollowthecontributingguidelines.
DjangoGirlsTutorial:Extensions
Introduction
Contributing
DjangoGirlsTutorial:Extensions
3Introduction
Yes,thisisthelastthingwewilldointhistutorial.Youhavealreadylearnedalot!Timetousethisknowledge.
Rememberthechapteraboutquerysets?Wecreatedaviewpost_listthatdisplaysonlypublishedblogposts(thosewithnon-emptypublished_date).
Timetodosomethingsimilar,butfordraftposts.
Let'saddalinkinblog/templates/blog/base.htmlnearthebuttonforaddingnewposts(justabove<h1><ahref="/">DjangoGirlsBlog</a></h1>line!):
<ahref="{%url"post_draft_list"%}"class="top-menu"><spanclass="glyphiconglyphicon-edit"></span></a>
Next:urls!Inblog/urls.pyweadd:
url(r'^drafts/$',views.post_draft_list,name='post_draft_list'),
Timetocreateaviewinblog/views.py:
defpost_draft_list(request):
posts=Post.objects.filter(published_date__isnull=True).order_by('created_date')
returnrender(request,'blog/post_draft_list.html',{'posts':posts})
ThislinePost.objects.filter(published_date__isnull=True).order_by('created_date')makessurewetakeonlyunpublishedposts(published_date__isnull=True)andorderthembycreated_date(order_by('created_date')).
Ok,thelastbitisofcourseatemplate!Createafileblog/templates/blog/post_draft_list.htmlandaddthefollowing:
{%extends"blog/base.html"%}
{%blockcontent%}
{%forpostinposts%}
<divclass="post">
<pclass="date">created:{{post.created_date|date:"d-m-Y"}}</p>
<h1><ahref="{%url"blog.views.post_detail"pk=post.pk%}">{{post.title}}</a></h1>
<p>{{post.text|truncatechars:200}}</p>
</div>
{%endfor%}
{%endblock%}
Itlooksverysimilartoourpost_list.html,right?
Nowwhenyougotohttp://127.0.0.1:8000/drafts/youwillseethelistofunpublishedposts.
Yay!Yourfirsttaskisdone!
Homework:addmoretoyourwebsite!
Pagewithlistofunpublishedposts
Addpublishbutton
DjangoGirlsTutorial:Extensions
4Homework:addmoretoyourwebsite!
Itwouldbenicetohaveabuttonontheblogpostdetailpagethatwillimmediatelypublishthepost,right?
Let'sopenblog/template/blog/post_detail.htmlandchangetheselines:
{%ifpost.published_date%}
{{post.published_date}}
{%endif%}
intothese:
{%ifpost.published_date%}
{{post.published_date}}
{%else%}
<aclass="btnbtn-default"href="{%url"blog.views.post_publish"pk=post.pk%}">Publish</a>
{%endif%}
Asyounoticed,weadded{%else%}linehere.Thatmeans,thatiftheconditionfrom{%ifpost.published_date%}isnotfulfilled(soifthereisnopublished_date),thenwewanttodotheline<aclass="btnbtn-default"href="{%url"post_publish"pk=post.pk%}">Publish</a>.Notethatwearepassingapkvariableinthe{%url%}.
TimetocreateaURL(inblog/urls.py):
url(r'^post/(?P<pk>[0-9]+)/publish/$',views.post_publish,name='post_publish'),
andfinally,aview(asalways,inblog/views.py):
defpost_publish(request,pk):
post=get_object_or_404(Post,pk=pk)
post.publish()
returnredirect('blog.views.post_detail',pk=pk)
Remember,whenwecreatedaPostmodelwewroteamethodpublish.Itlookedlikethis:
defpublish(self):
self.published_date=timezone.now()
self.save()
Nowwecanfinallyusethis!
Andonceagainafterpublishingthepostweareimmediatelyredirectedtothepost_detailpage!
DjangoGirlsTutorial:Extensions
5Homework:addmoretoyourwebsite!
Congratulations!Youarealmostthere.Thelaststepisaddingadeletebutton!
Let'sopenblog/templates/blog/post_detail.htmlonceagainandaddthisline:
<aclass="btnbtn-default"href="{%url"post_remove"pk=post.pk%}"><spanclass="glyphiconglyphicon-remove"></span></
justunderalinewiththeeditbutton.
NowweneedaURL(blog/urls.py):
url(r'^post/(?P<pk>[0-9]+)/remove/$',views.post_remove,name='post_remove'),
Now,timeforaview!Openblog/views.pyandaddthiscode:
defpost_remove(request,pk):
post=get_object_or_404(Post,pk=pk)
post.delete()
returnredirect('blog.views.post_list')
Theonlynewthingistoactuallydeleteablogpost.EveryDjangomodelcanbedeletedby.delete().Itisassimpleasthat!
Andthistime,afterdeletingapostwewanttogotothewebpagewithalistofposts,soweareusingredirect.
Let'stestit!Gotothepagewithapostandtrytodeleteit!
Deletepost
DjangoGirlsTutorial:Extensions
6Homework:addmoretoyourwebsite!
Yes,thisisthelastthing!Youcompletedthistutorial!Youareawesome!
DjangoGirlsTutorial:Extensions
7Homework:addmoretoyourwebsite!
Youmighthavenoticedthatyoudidn'thavetouseyourpassword,apartfrombackwhenweusedtheadmininterface.Youmightalsohavenoticedthatthismeansthatanyonecanaddoreditpostsinyourblog.Idon'tknowaboutyou,butIdon'twantjustanyonetopostonmyblog.Soletsdosomethingaboutit.
Firstletsmakethingssecure.Wewillprotectourpost_new,post_edit,post_draft_list,post_removeandpost_publishviewssothatonlylogged-inuserscanaccessthem.Djangoshipswithsomenicehelpersforthatusing,thekindofadvancedtopic,decorators.Don'tworryaboutthetechnicalitiesnow,youcanreadupontheselater.ThedecoratortouseisshippedinDjangointhemoduledjango.contrib.auth.decoratorsandiscalledlogin_required.
Soedityourblog/views.pyandaddtheselinesatthetopalongwiththerestoftheimports:
fromdjango.contrib.auth.decoratorsimportlogin_required
Thenaddalinebeforeeachofthepost_new,post_edit,post_draft_list,post_removeandpost_publishviews(decoratingthem)likethefollowing:
@login_required
defpost_new(request):
[...]
Thatsit!Nowtrytoaccesshttp://localhost:8000/post/new/,noticethedifference?
Ifyoujustgottheemptyform,youareprobablystillloggedinfromthechapterontheadmin-interface.Gotohttp://localhost:8000/admin/logout/tologout,thengotohttp://localhost:8000/post/newagain.
Youshouldgetoneofthebelovederrors.Thisoneisquiteinterestingactually:Thedecoratorweaddedbeforewillredirectyoutotheloginpage.Butthatisn'tavailableyet,soitraisesa"Pagenotfound(404)".
Don'tforgettoaddthedecoratorfromabovetopost_edit,post_remove,post_draft_listandpost_publishtoo.
Horray,wereachedpartofthegoal!Otherpeoplecan'tjustcreatepostsonourbloganymore.Unfortunatelywecan'tcreatepostsanymoretoo.Soletsfixthatnext.
Nowwecouldtrytodolotsofmagicstufftoimplementusersandpasswordsandauthenticationbutdoingthiskindofstuffcorrectlyisrathercomplicated.AsDjangois"batteriesincluded",someonehasdonethehardworkforus,sowewillmakefurtheruseoftheauthenticationstuffprovided.
Inyourmysite/urls.pyaddaurlurl(r'^accounts/login/$','django.contrib.auth.views.login').Sothefileshouldnowlooksimilartothis:
fromdjango.conf.urlsimportpatterns,include,url
fromdjango.contribimportadmin
Homework:Addingsecuritytoyourwebsite
Authorizingadd/editofposts
Loginusers
DjangoGirlsTutorial:Extensions
8Homework:secureyourwebsite
admin.autodiscover()
urlpatterns=patterns('',
url(r'^admin/',include(admin.site.urls)),
url(r'^accounts/login/$','django.contrib.auth.views.login'),
url(r'',include('blog.urls')),
)
Thenweneedatemplatefortheloginpage,socreateadirectoryblog/templates/registrationandafileinsidenamedlogin.html:
{%extends"blog/base.html"%}
{%blockcontent%}
{%ifform.errors%}
<p>Yourusernameandpassworddidn'tmatch.Pleasetryagain.</p>
{%endif%}
<formmethod="post"action="{%url"django.contrib.auth.views.login"%}">
{%csrf_token%}
<table>
<tr>
<td>{{form.username.label_tag}}</td>
<td>{{form.username}}</td>
</tr>
<tr>
<td>{{form.password.label_tag}}</td>
<td>{{form.password}}</td>
</tr>
</table>
<inputtype="submit"value="login"/>
<inputtype="hidden"name="next"value="{{next}}"/>
</form>
{%endblock%}
Youwillseethatthisalsomakesuseofourbase-templatefortheoveralllookandfeelofyourblog.
Thenicethinghereisthatthisjustworks[TM].Wedon'thavetodealwithhandlingoftheformssubmissionnorwithpasswordsandsecuringthem.Onlyonethingislefthere,weshouldaddasettingtomysite/settings.py:
LOGIN_REDIRECT_URL='/'
Nowwhentheloginisaccesseddirectly,itwillredirectsuccessfullogintothetoplevelindex.
Sonowwemadesurethatonlyauthorizedusers(ie.us)canadd,editorpublishposts.Butstilleveryonegetstoviewthebuttonstoaddoreditposts,letshidetheseforusersthataren'tloggedin.Forthisweneedtoeditthetemplates,soletsstartwiththebasetemplatefromblog/templates/blog/base.html:
<body>
<divclass="page-header">
{%ifuser.is_authenticated%}
<ahref="{%url"post_new"%}"class="top-menu"><spanclass="glyphiconglyphicon-plus"></span></a>
<ahref="{%url"post_draft_list"%}"class="top-menu"><spanclass="glyphiconglyphicon-edit"></span></a>
{%else%}
<ahref="{%url"django.contrib.auth.views.login"%}"class="top-menu"><spanclass="glyphiconglyphicon-lock"></
{%endif%}
<h1><ahref="{%url"blog.views.post_list"%}">DjangoGirls</a></h1>
Improvingthelayout
DjangoGirlsTutorial:Extensions
9Homework:secureyourwebsite
</div>
<divclass="content">
<divclass="row">
<divclass="col-md-8">
{%blockcontent%}
{%endblock%}
</div>
</div>
</div>
</body>
Youmightrecognizethepatternhere.Thereisanif-conditioninsidethetemplatethatchecksforauthenticateduserstoshowtheeditbuttons.Otherwiseitshowsaloginbutton.
Homework:Editthetemplateblog/templates/blog/post_detail.htmltoonlyshowtheeditbuttonsforauthenticatedusers.
Letsaddsomenicesugartoourtemplateswhileweareatit.Firstwewilladdsomestufftoshowthatweareloggedin.Editblog/templates/blog/base.htmllikethis:
<divclass="page-header">
{%ifuser.is_authenticated%}
<ahref="{%url"post_new"%}"class="top-menu"><spanclass="glyphiconglyphicon-plus"></span></a>
<ahref="{%url"post_draft_list"%}"class="top-menu"><spanclass="glyphiconglyphicon-edit"></span></a>
<pclass="top-menu">Hello{{user.username}}<small>(<ahref="{%url"django.contrib.auth.views.logout"%}">Logout
{%else%}
<ahref="{%url"django.contrib.auth.views.login"%}"class="top-menu"><spanclass="glyphiconglyphicon-lock"></span
{%endif%}
<h1><ahref="{%url"blog.views.post_list"%}">DjangoGirls</a></h1>
</div>
Thisaddsanice"Hello<username>"toreminduswhoweareandthatweareauthenticated.Alsothisaddsalinktologoutoftheblog.Butasyoumightnoticethisisn'tworkingyet.Ohnooz,webroketheinternetz!Letsfixit!
Wedecidedtorelyondjangotohandlelogin,letsseeifDjangocanalsohandlelogoutforus.Checkhttps://docs.djangoproject.com/en/1.6/topics/auth/default/andseeifyoufindsomething.
Donereading?Youshouldbynowthinkaboutaddingaurl(inmysite/urls.py)pointingtothedjango.contrib.auth.views.logoutview.Likethis:
fromdjango.conf.urlsimportpatterns,include,url
fromdjango.contribimportadmin
admin.autodiscover()
urlpatterns=patterns('',
url(r'^admin/',include(admin.site.urls)),
url(r'^accounts/login/$','django.contrib.auth.views.login'),
url(r'^accounts/logout/$','django.contrib.auth.views.logout',{'next_page':'/'}),
url(r'',include('blog.urls')),
)
Thatsit!Ifyoufollowedalloftheaboveuntilthispoint(anddidthehomework),younowhaveablogwhereyou
needausernameandpasswordtologin,needtobeloggedintoadd/edit/publish(/delete)posts
Moreonauthenticatedusers
DjangoGirlsTutorial:Extensions
10Homework:secureyourwebsite
andcanlogoutagain
DjangoGirlsTutorial:Extensions
11Homework:secureyourwebsite
NowweonlyhavePostmodel,whataboutreceivingsomefeedbackfromyourreaders?
Let'sopenblog/models.pyandappendthispieceofcodetotheendoffile:
classComment(models.Model):
post=models.ForeignKey('blog.Post',related_name='comments')
author=models.CharField(max_length=200)
text=models.TextField()
created_date=models.DateTimeField(default=timezone.now)
approved_comment=models.BooleanField(default=False)
defapprove(self):
self.approved_comment=True
self.save()
def__str__(self):
returnself.text
YoucangobacktoDjangomodelschapterintutorialifyouneedtoremindyourselfwhateachoffieldtypesmeans.
Inthischapterwehavenewtypeoffield:
models.BooleanField-thisistrue/falsefield.
Andrelated_nameoptioninmodels.ForeignKeyallowustohaveaccesstocommentsfrompostmodel.
Nowit'stimetoaddourcommentmodeltodatabase.TodothiswehavetoletknowDjangothatwemadechangesinourmodel.Typepythonmanage.pymakemigrationsblog.Justlikethis:
(myvenv)~/djangogirls$pythonmanage.pymakemigrationsblog
Migrationsfor'blog':
0002_comment.py:
-CreatemodelComment
Youcanseethatthiscommandcreatedforusanothermigrationfileinblog/migrations/directory.Nowweneedtoapplythosechangeswithpythonmanage.pymigrateblog.Itshouldlooklikethis:
(myvenv)~/djangogirls$pythonmanage.pymigrateblog
Operationstoperform:
Applyallmigrations:blog
Runningmigrations:
Renderingmodelstates...DONE
Applyingblog.0002_comment...OK
OurCommentmodelexistsindatabasenow.Itwouldbeniceifwehadaccesstoitinouradminpanel.
Homework:createcommentmodel
Creatingcommentblogmodel
Createtablesformodelsinyourdatabase
DjangoGirlsTutorial:Extensions
12Homework:createcommentmodel
Toregistermodelinadminpanel,gotoblog/admin.pyandaddline:
admin.site.register(Comment)
Don'tforgettoimportCommentmodel,fileshouldlooklikethis:
fromdjango.contribimportadmin
from.modelsimportPost,Comment
admin.site.register(Post)
admin.site.register(Comment)
Ifyoutypepythonmanage.pyrunserverincommandpromptandgotohttp://127.0.0.1:8000/admin/inyourbrowser,youshouldhaveaccesstolist,addandremovecomments.Don'thesitatetoplaywithit!
Gotoblog/templated/blog/post_detail.htmlfileandaddthoselinesbefore{%endblock%}tag:
<hr>
{%forcommentinpost.comments.all%}
<divclass="comment">
<divclass="date">{{comment.created_date}}</div>
<strong>{{comment.author}}</strong>
<p>{{comment.text|linebreaks}}</p>
</div>
{%empty%}
<p>Nocommentshereyet:(</p>
{%endfor%}
Nowwecanseethecommentssectiononpageswithpostdetails.
Butitcanlookalittlebitbetter,addsomecsstostatic/css/blog.css:
.comment{
margin:20px0px20px20px;
}
Wecanalsoletknowaboutcommentsonpostlistpage,gotoblog/templates/blog/post_list.htmlfileandaddline:
<ahref="{%url"blog.views.post_detail"pk=post.pk%}">Comments:{{post.comments.count}}</a>
Afterthatourtemplateshouldlooklikethis:
{%extends"blog/base.html"%}
{%blockcontent%}
{%forpostinposts%}
<divclass="post">
<divclass="date">
{{post.published_date}}
Registercommentmodelinadminpanel
Makeourcommentsvisible
DjangoGirlsTutorial:Extensions
13Homework:createcommentmodel
</div>
<h1><ahref="{%url"blog.views.post_detail"pk=post.pk%}">{{post.title}}</a></h1>
<p>{{post.text|linebreaks}}</p>
<ahref="{%url"blog.views.post_detail"pk=post.pk%}">Comments:{{post.comments.count}}</a>
</div>
{%endfor%}
{%endblockcontent%}
Rightnowwecanseecommentsonourblog,butwecannotaddthem,let'schangethat!
Gotoblog/forms.pyandaddthoselinestotheendofthefile:
classCommentForm(forms.ModelForm):
classMeta:
model=Comment
fields=('author','text',)
Don'tforgettoimportCommentmodel,changeline:
from.modelsimportPost
into:
from.modelsimportPost,Comment
Nowgotoblog/templates/blog/post_detail.htmlandbeforeline{%forcommentinpost.comments.all%}add:
<aclass="btnbtn-default"href="{%url"add_comment_to_post"pk=post.pk%}">Addcomment</a>
Gotopostdetailpageandyoushouldseeerror:
Let'sfixthis!Gotoblog/urls.pyandaddthispatterntourlpatterns:
url(r'^post/(?P<pk>[0-9]+)/comment/$',views.add_comment_to_post,name='add_comment_to_post'),
Nowyoushouldseethiserror:
Letyourreaderswritecomments
DjangoGirlsTutorial:Extensions
14Homework:createcommentmodel
Tofixthis,addthispieceofcodetoblog/views.py:
defadd_comment_to_post(request,pk):
post=get_object_or_404(Post,pk=pk)
ifrequest.method=="POST":
form=CommentForm(request.POST)
ifform.is_valid():
comment=form.save(commit=False)
comment.post=post
comment.save()
returnredirect('blog.views.post_detail',pk=post.pk)
else:
form=CommentForm()
returnrender(request,'blog/add_comment_to_post.html',{'form':form})
Don'tforgetaboutimportsatthebeginningofthefile:
from.formsimportPostForm,CommentForm
Nowyoushouldsee:
Likeerrormentions,templatedoesnotexist,createoneasblog/templates/blog/add_comment_to_post.htmlandaddthoselines:
{%extends"blog/base.html"%}
DjangoGirlsTutorial:Extensions
15Homework:createcommentmodel
{%blockcontent%}
<h1>Newcomment</h1>
<formmethod="POST"class="post-form">{%csrf_token%}
{{form.as_p}}
<buttontype="submit"class="savebtnbtn-default">Send</button>
</form>
{%endblock%}
Yay!Nowyourreaderscanletyouknowwhattheythinkbelowyourposts!
Notallofourcommentsshouldbedisplayed.Blogownershouldhaveoptiontoapproveordeletecomments.Let'sdosomethingaboutit.
Gotoblog/templates/blog/post_detail.htmlandchangelines:
{%forcommentinpost.comments.all%}
<divclass="comment">
<divclass="date">{{comment.created_date}}</div>
<strong>{{comment.author}}</strong>
<p>{{comment.text|linebreaks}}</p>
</div>
{%empty%}
<p>Nocommentshereyet:(</p>
{%endfor%}
to:
{%forcommentinpost.comments.all%}
{%ifuser.is_authenticatedorcomment.approved_comment%}
<divclass="comment">
<divclass="date">
{{comment.created_date}}
{%ifnotcomment.approved_comment%}
<aclass="btnbtn-default"href="{%url"comment_remove"pk=comment.pk%}"><spanclass="glyphiconglyphicon-remove"
<aclass="btnbtn-default"href="{%url"comment_approve"pk=comment.pk%}"><spanclass="glyphiconglyphicon-ok"
{%endif%}
</div>
<strong>{{comment.author}}</strong>
<p>{{comment.text|linebreaks}}</p>
</div>
{%endif%}
{%empty%}
<p>Nocommentshereyet:(</p>
{%endfor%}
YoushouldseeNoReverseMatch,becausenourlmatchescomment_removeandcomment_approvepatterns.
Addurlpatternstoblog/urls.py:
url(r'^comment/(?P<pk>[0-9]+)/approve/$',views.comment_approve,name='comment_approve'),
url(r'^comment/(?P<pk>[0-9]+)/remove/$',views.comment_remove,name='comment_remove'),
NowyoushouldseeAttributeError.Togetridofit,createmoreviewsinblog/views.py:
@login_required
defcomment_approve(request,pk):
Moderatingyourcomments
DjangoGirlsTutorial:Extensions
16Homework:createcommentmodel
comment=get_object_or_404(Comment,pk=pk)
comment.approve()
returnredirect('blog.views.post_detail',pk=comment.post.pk)
@login_required
defcomment_remove(request,pk):
comment=get_object_or_404(Comment,pk=pk)
post_pk=comment.post.pk
comment.delete()
returnredirect('blog.views.post_detail',pk=post_pk)
Andofcoursefiximports.
Everythingworks,butthereisonemisconception.Inourpostlistpageunderpostsweseenumberofallcommentsattached,butwewanttohavenumberofapprovedcommentsthere.
Gotoblog/templates/blog/post_list.htmlandchangeline:
<ahref="{%url"blog.views.post_detail"pk=post.pk%}">Comments:{{post.comments.count}}</a>
to:
<ahref="{%url"blog.views.post_detail"pk=post.pk%}">Comments:{{post.approved_comments.count}}</a>
AndalsoaddthismethodtoPostmodelinblog/models.py:
defapproved_comments(self):
returnself.comments.filter(approved_comment=True)
Nowyourcommentfeatureisfinished!Congrats!:-)
DjangoGirlsTutorial:Extensions
17Homework:createcommentmodel
PartofthischapterisbasedontutorialsbyGeekGirlsCarrots(http://django.carrots.pl/).
Partsofthischapterisbasedonthedjango-marcadortutoriallicensedunderCreativeCommonsAttribution-ShareAlike4.0InternationalLicense.Thedjango-marcadortutorialiscopyrightedbyMarkusZapke-Gründemannetal.
TheeasiestwaytoinstallPostgresonWindowsisusingaprogramyoucanfindhere:http://www.enterprisedb.com/products-services-training/pgdownload#windows
Choosethenewestversionavailableforyouroperatingsystem.Downloadtheinstaller,runitandthenfollowtheinstructionsavailablehere:http://www.postgresqltutorial.com/install-postgresql/.Takenoteoftheinstallationdirectoryasyouwillneeditinthenextstep(typically,it'sC:\ProgramFiles\PostgreSQL\9.3).
TheeasiestwayistodownloadthefreePostgres.appandinstallitlikeanyotherapplicationonyouroperatingsystem.
Downloadit,dragtotheApplicationsdirectoryandrunbydoubleclicking.That'sit!
You'llalsohavetoaddthePostgrescommandlinetoolstoyourPATHvariable,whatisdescribedhere.
Installationstepsvaryfromdistributiontodistribution.BelowarethecommandsforUbuntuandFedora,butifyou'reusingadifferentdistrotakealookatthePostgreSQLdocumentation.
Runthefollowingcommand:
sudoapt-getinstallpostgresqlpostgresql-contrib
Runthefollowingcommand:
sudoyuminstallpostgresql93-server
Nextup,weneedtocreateourfirstdatabase,andauserthatcanaccessthatdatabase.PostgreSQLletsyoucreateasmanydatabasesandusersasyoulike,soifyou'rerunningmorethanonesiteyoushouldcreateadatabaseforeachone.
PostgreSQLinstallation
Windows
MacOSX
Linux
Ubuntu
Fedora
Createdatabase
DjangoGirlsTutorial:Extensions
18Optional:PostgreSQLinstallation
Ifyou'reusingWindows,there'sacouplemorestepsweneedtocomplete.Fornowit'snotimportantforyoutounderstandtheconfigurationwe'redoinghere,butfeelfreetoaskyourcoachifyou'recuriousastowhat'sgoingon.
1. OpentheCommandPrompt(Startmenu→AllPrograms→Accessories→CommandPrompt)2. Runthefollowingbytypingitinandhittingreturn:setxPATH"%PATH%;C:\ProgramFiles\PostgreSQL\9.3\bin".Youcan
pastethingsintotheCommandPromptbyrightclickingandselectingPaste.Makesurethatthepathisthesameoneyounotedduringinstallationwith\binaddedattheend.YoushouldseethemessageSUCCESS:Specifiedvaluewassaved..
3. CloseandthenreopentheCommandPrompt.
First,let'slaunchthePostgresconsolebyrunningpsql.Rememberhowtolaunchtheconsole?
OnMacOSXyoucandothisbylaunchingtheTerminalapplication(it'sinApplications→Utilities).OnLinux,it'sprobablyunderApplications→Accessories→Terminal.OnWindowsyouneedtogotoStartmenu→AllPrograms→Accessories→CommandPrompt.Furthermore,onWindows,psqlmightrequirelogginginusingtheusernameandpasswordyouchoseduringinstallation.Ifpsqlisaskingyouforapasswordanddoesn'tseemtowork,trypsql-U<username>-Wfirstandenterthepasswordlater.
$psql
psql(9.3.4)
Type"help"forhelp.
#
Our$nowchangedinto#,whichmeansthatwe'renowsendingcommandstoPostgreSQL.Let'screateauser:
#CREATEUSERname;
CREATEROLE
Replacenamewithyourownname.Youshouldn'tuseaccentedlettersorwhitespace(e.g.bożenamariaisinvalid-youneedtoconvertitintobozena_maria).
Nowit'stimetocreateadatabaseforyourDjangoproject:
#CREATEDATABASEdjangogirlsOWNERname;
CREATEDATABASE
Remembertoreplacenamewiththenameyou'vechosen(e.g.bozena_maria).
Great-that'sdatabasesallsorted!
Findthispartinyourmysite/settings.pyfile:
DATABASES={
'default':{
Windows
Createthedatabase
Updatingsettings
DjangoGirlsTutorial:Extensions
19Optional:PostgreSQLinstallation
'ENGINE':'django.db.backends.sqlite3',
'NAME':os.path.join(BASE_DIR,'db.sqlite3'),
}
}
Andreplaceitwiththis:
DATABASES={
'default':{
'ENGINE':'django.db.backends.postgresql_psycopg2',
'NAME':'djangogirls',
'USER':'name',
'PASSWORD':'',
'HOST':'localhost',
'PORT':'',
}
}
Remebertochangenametotheusernamethatyoucreatedearlierinthischapter.
First,installHerokuToolbeltfromhttps://toolbelt.heroku.com/Whilewewillneedthismostlyfordeployingyoursitelateron,italsoincludesGit,whichmightcomeinhandyalready.
Nextup,weneedtoinstallapackagewhichletsPythontalktoPostgreSQL-thisiscalledpsycopg2.TheinstallationinstructionsdifferslightlybetweenWindowsandLinux/OSX.
ForWindows,downloadthepre-builtfilefromhttp://www.stickpeople.com/projects/python/win-psycopg/
MakesureyougettheonecorrespondingtoyourPythonversion(3.4shouldbethelastline)andtothecorrectarchitecture(32bitintheleftcolumnor64bitintherightcolumn).
Renamethedownloadedfileandmoveitsothatit'snowavailableatC:\psycopg2.exe.
Oncethat'sdone,enterthefollowingcommandintheterminal(makesureyourvirtualenvisactivated):
easy_installC:\psycopg2.exe
Runthefollowinginyourconsole:
(myvenv)~/djangogirls$pipinstallpsycopg2
Ifthatgoeswell,you'llseesomethinglikethis
Downloading/unpackingpsycopg2
Installingcollectedpackages:psycopg2
InstallingPostgreSQLpackageforPython
Windows
LinuxandOSX
DjangoGirlsTutorial:Extensions
20Optional:PostgreSQLinstallation
Successfullyinstalledpsycopg2
Cleaningup...
Oncethat'scompleted,runpython-c"importpsycopg2".Ifyougetnoerrors,everything'sinstalledsuccessfully.
DjangoGirlsTutorial:Extensions
21Optional:PostgreSQLinstallation
PythonAnywheregaveyouafreedomain,butmaybeyoudon'twanttohave".pythonanywhere.com"attheendofyourblogURL.Maybeyouwantyourblogtojustliveat"www.infinite-kitten-pictures.org"or"www.3d-printed-steam-engine-parts.com"or"www.antique-buttons.com"or"www.mutant-unicornz.net",orwhateverit'llbe.
Herewe'lltalkabitaboutwheretogetadomain,andhowtohookituptoyourwebapponPythonAnywhere.However,youshouldknowthatmostdomainscostmoney,andPythonAnywerealsochargesamonthlyfeetouseyourowndomainname--it'snotmuchmoneyintotal,butthisisprobablysomethingyouonlywanttodoifyou'rereallycommitted!
Atypicaldomaincostsaround$15ayear.Therearecheaperandmoreexpensiveoptions,dependingontheprovider.Therearealotofcompaniesthatyoucanbuyadomainfrom:asimplegooglesearchwillgivehundredsofoptions.
OurfavouriteoneisIwantmyname.Theyadvertiseas"painlessdomainmanagement"anditreallyispainless.
Youcanalsogetdomainsforfree.dot.tkisoneplacetogetone,butyoushouldbeawarethatfreedomainssometimesfeelabitcheap--ifyoursiteisgoingtobeforaprofessionalbusiness,youmightwanttothinkaboutpayingfora"proper"domainthatendsin.com.
Ifyouwentthroughiwantmyname.com,clickDomainsinthemenuandchooseyournewlypurchaseddomain.ThenlocateandclickonthemanageDNSrecordslink:
Nowyouneedtolocatethisform:
Andfillitinwiththefollowingdetails:
Hostname:wwwType:CNAMEValue:yourdomainfromPythonAnywhere(forexampledjangogirls.pythonanywhere.com)TTL:60
ClicktheAddbuttonandSavechangesatthebottom.
Domain
Wheretoregisteradomain?
HowtopointyourdomainatPythonAnywhere
DjangoGirlsTutorial:Extensions
22Optional:Domain
NoteIfyouusedadifferentdomainprovider,theexactUIforfindingyourDNS/CNAMEsettingswillbedifferent,butyourobjectiveisthesame:tosetupaCNAMEthatpointsyournewdomainatyourusername.pythonanywhere.com.
Itcantakeafewminutesforyourdomaintostartworking,sobepatient!
YoualsoneedtotellPythonAnywherethatyouwanttouseyourcustomdomain.
GotothePythonAnywhereAccountspageandupgradeyouraccount.Thecheapestoption(a"Hacker"plan)isfinetostartwith,youcanalwaysupgradeitlaterwhenyougetsuper-famousandhavemillionsofhits.
Next,goovertotheWebtabandnotedownacoupleofthings:
CopythepathtoyourvirtualenvandputitsomewheresafeClickthroughtoyourwsgiconfigfile,copythecontents,andpastethemsomewheresafe.
Next,Deleteyouroldwebapp.Don'tworry,thisdoesn'tdeleteanyofyourcode,itjustswitchesoffthedomainatyourusername.pythonanywhere.com.Next,createanewwebapp,andfollowthesesteps:
EnteryournewdomainnameChoose"manualconfiguration"PickPython3.4Andwe'redone!
Whenyougettakenbacktothewebtab.
PasteinthevirtualenvpathyousavedearlierClickthroughtothewsgiconfigurationfile,andpasteinthecontentsfromyouroldconfigfile
Hitreloadwebapp,andyoushouldfindyoursiteisliveonitsnewdomain!
Ifyourunintoanyproblems,hitthe"Sendfeedback"linkonthePythonAnywheresite,andoneoftheirfriendlyadminswillbetheretohelpyouinnotime.
ConfigurethedomainviaawebapponPythonAnywhere.
DjangoGirlsTutorial:Extensions
23Optional:Domain
It'salwaysgoodforadevelopertohaveacoupleofdifferentdeploymentoptionsundertheirbelt.WhynottrydeployingyoursitetoHeroku,aswellasPythonAnywhere?
Herokuisalsofreeforsmallapplicationsthatdon'thavetoomanyvisitors,butit'sabitmoretrickytogetdeployed.
Wewillbefollowingthistutorial:https://devcenter.heroku.com/articles/getting-started-with-django,butwepasteditheresoit'seasierforyou.
Ifyoudidn'tcreateonebefore,weneedtocreatearequirements.txtfiletotellHerokuwhatPythonpackagesneedtobeinstalledonourserver.
Butfirst,Herokuneedsustoinstallafewnewpackages.Gotoyourconsolewithvirtualenvactivatedandtypethis:
(myvenv)$pipinstalldj-database-urlgunicornwhitenoise
Aftertheinstallationisfinished,gotothedjangogirlsdirectoryandrunthiscommand:
(myvenv)$pipfreeze>requirements.txt
Thiswillcreateafilecalledrequirements.txtwithalistofyourinstalledpackages(i.e.Pythonlibrariesthatyouareusing,forexampleDjango:)).
Note:pipfreezeoutputsalistofallthePythonlibrariesinstalledinyourvirtualenv,andthe>takestheoutputofpipfreezeandputsitintoafile.Tryrunningpipfreezewithoutthe>requirements.txttoseewhathappens!
Openthisfileandaddthefollowinglineatthebottom:
psycopg2==2.5.4
ThislineisneededforyourapplicationtoworkonHeroku.
AnotherthingHerokuwantsisaProcfile.ThistellsHerokuwhichcommandstoruninordertostartourwebsite.Openupyourcodeeditor,createafilecalledProcfileindjangogirlsdirectoryandaddthisline:
web:gunicornmysite.wsgi
Thislinemeansthatwe'regoingtobedeployingawebapplication,andwe'lldothatbyrunningthecommandgunicornmysite.wsgi(gunicornisaprogramthat'slikeamorepowerfulversionofDjango'srunservercommand).
Thensaveit.Done!
DeploytoHeroku(aswellasPythonAnyhere)
Therequirements.txtfile
Procfile
DjangoGirlsTutorial:Extensions
24DeployyourwebsiteonHeroku
WealsoneedtotellHerokuwhichPythonversionwewanttouse.Thisisdonebycreatingaruntime.txtinthedjangogirlsdirectoryusingyoureditor's"newfile"command,andputtingthefollowingtext(andnothingelse!)inside:
python-3.4.2
Becauseit'smorerestrictivethanPythonAnywhere,Herokuwantstousedifferentsettingsfromtheonesweuseonourlocally(onourcomputer).HerokuwantstousePostgreswhileweuseSQLiteforexample.That'swhyweneedtocreateaseparatefileforsettingsthatwillonlybeavailableforourlocalenvironment.
Goaheadandcreatemysite/local_settings.pyfile.ItshouldcontainyourDATABASEsetupfromyourmysite/settings.pyfile.Justlikethat:
importos
BASE_DIR=os.path.dirname(os.path.dirname(__file__))
DATABASES={
'default':{
'ENGINE':'django.db.backends.sqlite3',
'NAME':os.path.join(BASE_DIR,'db.sqlite3'),
}
}
DEBUG=True
Thenjustsaveit!:)
Anotherthingweneedtodoismodifyourwebsite'ssettings.pyfile.Openmysite/settings.pyinyoureditorandaddthefollowinglinesattheendofthefile:
importdj_database_url
DATABASES['default']=dj_database_url.config()
SECURE_PROXY_SSL_HEADER=('HTTP_X_FORWARDED_PROTO','https')
ALLOWED_HOSTS=['*']
DEBUG=False
try:
from.local_settingsimport*
exceptImportError:
pass
It'lldonecessaryconfigurationforHerokuandalsoit'llimportallofyourlocalsettingsifmysite/local_settings.pyexists.
Thensavethefile.
Theruntime.txtfile
mysite/local_settings.py
mysite/settings.py
mysite/wsgi.py
DjangoGirlsTutorial:Extensions
25DeployyourwebsiteonHeroku
Openthemysite/wsgi.pyfileandaddtheselinesattheend:
fromwhitenoise.djangoimportDjangoWhiteNoise
application=DjangoWhiteNoise(application)
Allright!
YouneedtoinstallyourHerokutoolbeltwhichyoucanfindhere(youcanskiptheinstallationifyou'vealreadyinstalleditduringsetup):https://toolbelt.heroku.com/
WhenrunningtheHerokutoolbeltinstallationprogramonWindowsmakesuretochoose"CustomInstallation"whenbeingaskedwhichcomponentstoinstall.Inthelistofcomponentsthatshowsupafterthatpleaseadditionallycheckthecheckboxinfrontof"GitandSSH".
OnWindowsyoualsomustrunthefollowingcommandtoaddGitandSSHtoyourcommandprompt'sPATH:setxPATH"%PATH%;C:\ProgramFiles\Git\bin".Restartthecommandpromptprogramafterwardstoenablethechange.
Afterrestartingyourcommandprompt,don'tforgettogotoyourdjangogirlsfolderagainandactivateyourvirtualenv!(Hint:ChecktheDjangoinstallationchapter)
PleasealsocreateafreeHerokuaccounthere:https://id.heroku.com/signup/www-home-top
ThenauthenticateyourHerokuaccountonyourcomputerbyrunningthiscommand:
$herokulogin
Incaseyoudon'thaveanSSHkeythiscommandwillautomaticallycreateone.SSHkeysarerequiredtopushcodetotheHeroku.
Herokuusesgitforitsdeployments.UnlikePythonAnywhere,youcanpushtoHerokudirectly,withoutgoingviaGithub.Butweneedtotweakacoupleofthingsfirst.
Openthefilenamed.gitignoreinyourdjangogirlsdirectoryandaddlocal_settings.pytoit.Wewantgittoignorelocal_settings,soitstaysonourlocalcomputeranddoesn'tenduponHeroku.
*.pyc
db.sqlite3
myvenv
__pycache__
local_settings.py
Andwecommitourchanges
$gitstatus
$gitadd-A.
$gitcommit-m"additionalfilesandchangesforHeroku"
Herokuaccount
Gitcommit
DjangoGirlsTutorial:Extensions
26DeployyourwebsiteonHeroku
We'llbemakingyourblogavailableontheWebat[yourblog'sname].herokuapp.com,soweneedtochooseanamethatnobodyelsehastaken.Thisnamedoesn'tneedtoberelatedtotheDjangoblogapportomysiteoranythingwe'vecreatedsofar.Thenamecanbeanythingyouwant,butHerokuisquitestrictastowhatcharactersyoucanuse:you'reonlyallowedtousesimplelowercaseletters(nocapitallettersoraccents),numbers,anddashes(-).
Onceyou'vethoughtofaname(maybesomethingwithyournameornicknameinit),runthiscommand,replacingdjangogirlsblogwithyourownapplicationname:
$herokucreatedjangogirlsblog
Note:RemembertoreplacedjangogirlsblogwiththenameofyourapplicationonHeroku.
Ifyoucan'tthinkofaname,youcaninsteadrun
$herokucreate
andHerokuwillpickanunusednameforyou(probablysomethinglikeenigmatic-cove-2527).
IfyoueverfeellikechangingthenameofyourHerokuapplication,youcandosoatanytimewiththiscommand(replacethe-new-namewiththenewnameyouwanttouse):
$herokuapps:renamethe-new-name
Note:Rememberthatafteryouchangeyourapplication'sname,you'llneedtovisit[the-new-name].herokuapp.comtoseeyoursite.
Thatwasalotofconfigurationandinstalling,right?Butyouonlyneedtodothatonce!Nowyoucandeploy!
Whenyouranherokucreate,itautomaticallyaddedtheHerokuremoteforourapptoourrepository.Nowwecandoasimplegitpushtodeployourapplication:
$gitpushherokumaster
Note:Thiswillprobablyproducealotofoutputthefirsttimeyourunit,asHerokucompilesandinstallspsycopg.You'llknowit'ssucceededifyouseesomethinglikehttps://yourapplicationname.herokuapp.com/deployedtoHerokuneartheendoftheoutput.
You’vedeployedyourcodetoHeroku,andspecifiedtheprocesstypesinaProcfile(wechoseawebprocesstypeearlier).WecannowtellHerokutostartthiswebprocess.
Todothat,runthefollowingcommand:
Pickanapplicationname
DeploytoHeroku!
Visityourapplication
DjangoGirlsTutorial:Extensions
27DeployyourwebsiteonHeroku
$herokups:scaleweb=1
ThistellsHerokutorunjustoneinstanceofourwebprocess.Sinceourblogapplicationisquitesimple,wedon'tneedtoomuchpowerandsoit'sfinetorunjustoneprocess.It'spossibletoaskHerokutorunmoreprocesses(bytheway,Herokucallstheseprocesses"Dynos"sodon'tbesurprisedifyouseethisterm)butitwillnolongerbefree.
Wecannowvisittheappinourbrowserwithherokuopen.
$herokuopen
Note:youwillseeanerrorpage!We'lltalkaboutthatinaminute.
Thiswillopenaurllikehttps://djangogirlsblog.herokuapp.com/inyourbrowser,andatthemomentyouwillprobablyseeanerrorpage.
TheerroryousawwasbecausewewhenwedeployedtoHeroku,wecreatedanewdatabaseandit'sempty.Weneedtorunthemigrateandcreatesuperusercommands,justlikewedidonPythonAnywhere.Thistime,theycomeviaaspecialcommand-lineonourowncomputer,herokurun:
$herokurunpythonmanage.pymigrate
$herokurunpythonmanage.pycreatesuperuser
Thecommandpromptwillaskyoutochooseausernameandapasswordagain.Thesewillbeyourlogindetailsonyourlivewebsite'sadminpage.
Refreshitinyourbrowser,andthereyougo!Younowknowhowtodeploytotwodifferenthostingplatforms.Pickyourfavourite:)
DjangoGirlsTutorial:Extensions
28DeployyourwebsiteonHeroku