django girls tutorial extensions

28

Upload: thiago-monteiro

Post on 23-Jan-2016

60 views

Category:

Documents


5 download

DESCRIPTION

Django Girls Tutorial

TRANSCRIPT

Page 1: Django Girls Tutorial Extensions
Page 2: Django Girls Tutorial Extensions

1. Introduction2. Homework:addmoretoyourwebsite!3. Homework:secureyourwebsite4. Homework:createcommentmodel5. Optional:PostgreSQLinstallation6. Optional:Domain7. DeployyourwebsiteonHeroku

TableofContents

DjangoGirlsTutorial:Extensions

2

Page 3: Django Girls Tutorial Extensions

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

Page 4: Django Girls Tutorial Extensions

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!

Page 5: Django Girls Tutorial Extensions

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!

Page 6: Django Girls Tutorial Extensions

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!

Page 7: Django Girls Tutorial Extensions

Yes,thisisthelastthing!Youcompletedthistutorial!Youareawesome!

DjangoGirlsTutorial:Extensions

7Homework:addmoretoyourwebsite!

Page 8: Django Girls Tutorial Extensions

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

Page 9: Django Girls Tutorial Extensions

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

Page 10: Django Girls Tutorial Extensions

</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

Page 11: Django Girls Tutorial Extensions

andcanlogoutagain

DjangoGirlsTutorial:Extensions

11Homework:secureyourwebsite

Page 12: Django Girls Tutorial Extensions

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

Page 13: Django Girls Tutorial Extensions

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

Page 14: Django Girls Tutorial Extensions

</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

Page 15: Django Girls Tutorial Extensions

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

Page 16: Django Girls Tutorial Extensions

{%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

Page 17: Django Girls Tutorial Extensions

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

Page 18: Django Girls Tutorial Extensions

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

Page 19: Django Girls Tutorial Extensions

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

Page 20: Django Girls Tutorial Extensions

'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

Page 21: Django Girls Tutorial Extensions

Successfullyinstalledpsycopg2

Cleaningup...

Oncethat'scompleted,runpython-c"importpsycopg2".Ifyougetnoerrors,everything'sinstalledsuccessfully.

DjangoGirlsTutorial:Extensions

21Optional:PostgreSQLinstallation

Page 22: Django Girls Tutorial Extensions

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

Page 23: Django Girls Tutorial Extensions

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

Page 24: Django Girls Tutorial Extensions

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

Page 25: Django Girls Tutorial Extensions

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

Page 26: Django Girls Tutorial Extensions

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

Page 27: Django Girls Tutorial Extensions

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

Page 28: Django Girls Tutorial Extensions

$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